Java自旋锁与读写锁的实现原理

 更新时间:2026年02月13日 09:26:31   作者:康小庄  
本文介绍了Java中的自旋锁和读写锁,自旋锁是一种非阻塞锁,适用于锁持有时间极短的场景,本文结合实例代码给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友参考下吧

Java自旋锁与读写锁

一、自旋锁的核心概念

自旋锁(Spin Lock)是一种非阻塞锁,它的核心思想是:当线程获取锁失败时,不会立刻进入阻塞(休眠)状态,而是在一个循环中不断尝试获取锁(“自旋”),直到成功获取锁或者达到自旋上限。

二、对比传统阻塞锁(如 synchronized)

  • 阻塞锁:获取锁失败 → 线程进入等待队列 → 内核态切换 → 等待被唤醒 → 再次竞争锁(上下文切换成本高)
  • 自旋锁:获取锁失败 → 循环重试(用户态) → 成功获取锁(无上下文切换)

自旋锁适合锁持有时间极短的场景,因为短时间的自旋消耗的 CPU 资源,远小于线程上下文切换的开销;但如果锁持有时间长,自旋会浪费大量 CPU 资源(空转)。

三、Java 中自旋锁的实现原理

Java 中实现自旋锁的核心是CAS(Compare And Swap,比较并交换) 操作,这是一种原子操作,保证多线程下的线程安全。

CAS 有三个核心参数:

  • V:要修改的变量(锁的状态标识)
  • A:预期值(期望的锁状态,比如未被占用)
  • B:新值(修改后的锁状态,比如被当前线程占用)

只有当 V == A 时,才会将 V 更新为 B,否则不做操作,返回 false。

import java.util.concurrent.atomic.AtomicReference;
/**
 * 简单的自旋锁实现
 * 核心:利用AtomicReference的CAS操作保证锁的原子性竞争
 */
public class SimpleSpinLock {
    // 用AtomicReference存储持有锁的线程,初始为null(无锁状态)
    private final AtomicReference<Thread> lockOwner = new AtomicReference<>();
    // 获取锁(自旋逻辑)
    public void lock() {
        Thread currentThread = Thread.currentThread();
        // 自旋:CAS尝试将lockOwner从null改为当前线程,失败则循环重试
        while (!lockOwner.compareAndSet(null, currentThread)) {
            // 空循环(自旋),也可以加入自旋次数限制、yield()等优化
            // Thread.yield(); // 让出CPU,减少空转消耗
        }
        System.out.println(currentThread.getName() + " 获取到锁");
    }
    // 释放锁
    public void unlock() {
        Thread currentThread = Thread.currentThread();
        // 只有持有锁的线程才能释放锁
        if (lockOwner.get() == currentThread) {
            lockOwner.compareAndSet(currentThread, null);
            System.out.println(currentThread.getName() + " 释放了锁");
        } else {
            throw new IllegalMonitorStateException("当前线程未持有锁,无法释放");
        }
    }
    // 测试自旋锁
    public static void main(String[] args) {
        SimpleSpinLock spinLock = new SimpleSpinLock();
        // 创建5个线程竞争锁
        for (int i = 0; i < 5; i++) {
            new Thread(() -> {
                try {
                    spinLock.lock();
                    // 模拟持有锁的短时间操作(自旋锁适合短任务)
                    Thread.sleep(100);
                } catch (InterruptedException e) {
                    Thread.currentThread().interrupt();
                } finally {
                    spinLock.unlock();
                }
            }, "线程" + i).start();
        }
    }
}

四、Java 中的内置自旋锁优化

JDK 中并没有直接暴露 “自旋锁” 类,但在底层对synchronizedLock(如 ReentrantLock)做了自旋优化:

  1. synchronized 的自旋优化:JDK1.6 后,synchronized 引入了 “偏向锁→轻量级锁→重量级锁” 的升级过程,其中轻量级锁阶段会采用自旋(默认自旋 10 次,可通过 JVM 参数-XX:PreBlockSpin调整)。
  2. LockSupport.parkNanos():ReentrantLock 的底层(AQS)会在自旋一定次数后,调用parkNanos()让线程短暂休眠,避免无限自旋浪费 CPU(自适应自旋)。

一、读写锁的核心概念

读写锁(ReadWriteLock)是一种共享 - 排他锁,它把对资源的访问分成了 “读操作” 和 “写操作”,核心设计目标是:

  • 读锁(共享锁):多个线程可以同时获取读锁,互不阻塞(读 - 读共享)。
  • 写锁(排他锁 / 独占锁):只有一个线程能获取写锁,且写锁会阻塞所有读锁和其他写锁(写 - 读、写 - 写互斥)。

读写锁的核心规则

场景是否允许
已有读锁,加读锁允许(共享)
已有读锁,加写锁不允许(互斥)
已有写锁,加读锁不允许(互斥)
已有写锁,加写锁不允许(互斥)

二、Java 中的读写锁实现:ReentrantReadWriteLock

Java 在java.util.concurrent.locks包中提供了ReadWriteLock接口,其核心实现类是ReentrantReadWriteLock(可重入读写锁),它具备以下特性:

  1. 可重入:读线程获取读锁后可再次获取读锁,写线程获取写锁后可再次获取写锁 / 读锁。
  2. 公平 / 非公平模式:默认非公平,可通过构造函数指定公平模式。
  3. 锁降级:写锁可降级为读锁(写锁→读锁),但读锁不能升级为写锁。

“缓存模拟” 案例(典型的多读少写场景),展示读写锁的使用:

import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.locks.ReentrantReadWriteLock;
/**
 * 基于读写锁实现的线程安全缓存
 * 场景:多读少写,读操作并行,写操作独占
 */
public class ReadWriteLockCache {
    // 缓存容器
    private final Map<String, Object> cache = new HashMap<>();
    // 读写锁(默认非公平模式)
    private final ReentrantReadWriteLock rwLock = new ReentrantReadWriteLock();
    // 读锁(共享锁)
    private final ReentrantReadWriteLock.ReadLock readLock = rwLock.readLock();
    // 写锁(排他锁)
    private final ReentrantReadWriteLock.WriteLock writeLock = rwLock.writeLock();
    // 从缓存读取数据(读操作,用读锁)
    public Object get(String key) {
        // 获取读锁
        readLock.lock();
        try {
            System.out.println(Thread.currentThread().getName() + " 读取缓存,key=" + key);
            // 模拟读操作耗时
            Thread.sleep(100);
            return cache.get(key);
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
            return null;
        } finally {
            // 释放读锁(必须在finally中释放,避免锁泄漏)
            readLock.unlock();
            System.out.println(Thread.currentThread().getName() + " 释放读锁");
        }
    }
    // 向缓存写入数据(写操作,用写锁)
    public void put(String key, Object value) {
        // 获取写锁
        writeLock.lock();
        try {
            System.out.println(Thread.currentThread().getName() + " 写入缓存,key=" + key + ", value=" + value);
            // 模拟写操作耗时
            Thread.sleep(200);
            cache.put(key, value);
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
        } finally {
            // 释放写锁
            writeLock.unlock();
            System.out.println(Thread.currentThread().getName() + " 释放写锁");
        }
    }
    // 清空缓存(写操作,用写锁)
    public void clear() {
        writeLock.lock();
        try {
            System.out.println(Thread.currentThread().getName() + " 清空缓存");
            cache.clear();
        } finally {
            writeLock.unlock();
        }
    }
    // 测试读写锁
    public static void main(String[] args) {
        ReadWriteLockCache cache = new ReadWriteLockCache();
        // 1. 启动5个读线程(并行执行)
        for (int i = 0; i < 5; i++) {
            new Thread(() -> cache.get("user_1"), "读线程" + i).start();
        }
        // 2. 启动2个写线程(串行执行,且阻塞读线程)
        new Thread(() -> cache.put("user_1", "张三"), "写线程1").start();
        new Thread(() -> cache.put("user_2", "李四"), "写线程2").start();
    }
}

运行代码会发现,5 个读线程几乎同时执行(读锁共享),而 2 个写线程串行执行,且写线程执行时读线程会等待。

读线程0 读取缓存,key=user_1
读线程2 读取缓存,key=user_1
读线程1 读取缓存,key=user_1
读线程4 读取缓存,key=user_1
读线程3 读取缓存,key=user_1
读线程1 释放读锁
读线程3 释放读锁
读线程4 释放读锁
读线程2 释放读锁
写线程1 写入缓存,key=user_1, value=张三
读线程0 释放读锁
写线程2 写入缓存,key=user_2, value=李四
写线程1 释放写锁
写线程2 释放写锁

四、读写锁的关键特性详解

1. 可重入性
  • 读线程获取读锁后,可再次获取读锁(重入次数累加)。
  • 写线程获取写锁后,可再次获取写锁,也可获取读锁(锁降级的基础)。
  • 示例(锁降级):
// 写锁降级为读锁
writeLock.lock();
try {
    // 执行写操作
    cache.put("key", "value");
    // 降级:先获取读锁,再释放写锁
    readLock.lock();
} finally {
    writeLock.unlock();
}
try {
    // 持有读锁,安全读取数据
    System.out.println(cache.get("key"));
} finally {
    readLock.unlock();
}
2. 公平 / 非公平模式
  • 非公平模式(默认):性能更高,写线程可能插队(读线程多的时候,写线程可能饥饿)。
  • 公平模式:通过构造函数new ReentrantReadWriteLock(true)指定,按线程等待顺序获取锁,避免写饥饿,但性能略低。
3. 写饥饿问题

如果读操作非常频繁,写线程可能长期无法获取写锁(一直被读锁阻塞),解决方案:

  • 使用公平模式。
  • 控制读锁持有时间,避免长时间占用读锁。

到此这篇关于Java自旋锁与读写锁的实现原理的文章就介绍到这了,更多相关java自旋锁与读写锁内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

相关文章

  • java多线程学习笔记之自定义线程池

    java多线程学习笔记之自定义线程池

    本篇文章主要介绍了java多线程学习笔记之自定义线程池 ,小编觉得挺不错的,现在分享给大家,也给大家做个参考。一起跟随小编过来看看吧
    2017-09-09
  • java实现汉字转拼音

    java实现汉字转拼音

    这篇文章主要介绍了java实现汉字转拼音的功能,感兴趣的小伙伴们可以参考一下
    2015-12-12
  • 从Maven到Spring Boot实战教程

    从Maven到Spring Boot实战教程

    本文系统梳理了Java后端开发的核心技术栈,从Maven项目管理到Spring Boot实战,从MyBatis持久层到Spring MVC Web开发,涵盖了项目开发的全流程与关键知识点,感兴趣的朋友跟随小编一起看看吧
    2025-11-11
  • springboot验证码的生成与验证的两种方法

    springboot验证码的生成与验证的两种方法

    本文主要介绍了springboot验证码的生成与验证的两种方法,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2022-06-06
  • 在实践中了解Java反射机制应用

    在实践中了解Java反射机制应用

    当程序运行时,允许改变程序结构或变量类型,这种语言称为动态语言。我们认为java并不是动态语言,但是它却有一个非常突出的动态相关机制,俗称:反射。下面我们来简单学习一下吧
    2019-05-05
  • java 伪造http请求ip地址的方法

    java 伪造http请求ip地址的方法

    这篇文章主要介绍了java 伪造http请求ip地址的方法,小编觉得挺不错的,现在分享给大家,也给大家做个参考。一起跟随小编过来看看吧
    2018-09-09
  • Java并发编程总结——慎用CAS详解

    Java并发编程总结——慎用CAS详解

    下面小编就为大家带来一篇Java并发编程总结——慎用CAS详解。小编觉得挺不错的, 现在就分享给大家,也给大家做个参考。一起跟随小编过来看看吧
    2016-06-06
  • Mybatis中多个对象包含同一个对象的处理操作

    Mybatis中多个对象包含同一个对象的处理操作

    这篇文章主要介绍了Mybatis中多个对象包含同一个对象的处理操作,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教
    2021-06-06
  • java连接池Druid获取连接getConnection示例详解

    java连接池Druid获取连接getConnection示例详解

    这篇文章主要为大家介绍了java连接池Druid获取连接getConnection示例详解,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步,早日升职加薪
    2023-09-09
  • SpringBoot如何对密码等敏感信息进行脱敏处理

    SpringBoot如何对密码等敏感信息进行脱敏处理

    这篇文章主要为大家详细介绍了SpringBoot对密码等敏感信息进行脱敏处理的几个常用方法,文中的示例代码讲解详细,感兴趣的小伙伴可以了解下
    2025-05-05

最新评论