JDK读写锁ReadWriteLock的升级和降级问题

本文探讨了JDK中的ReadWriterLock及其实现ReentrantReadWriteLock的锁升级和降级概念。锁升级是指从读锁转为写锁,而锁降级是从写锁转换为读锁。由于写锁具有更高的并发限制,因此升级和降级命名由此而来。ReentrantReadWriteLock不支持锁升级,即在线程持有读锁时尝试获取写锁会导致死锁。然而,它支持锁降级,但降级过程中必须显式释放写锁,否则可能导致其他线程无法获取写锁。

JDK提供了写锁接口ReadWriteLock和它的实现ReentrantReadWriteLock。要实现一个读写锁,需要考虑很多细节,其中之一就是锁升级和锁降级的问题。什么是升级和降级呢?ReadWriteLock的javadoc有一段话:

Can the write lock be downgraded to a read lock without allowing an intervening writer? Can a read lock be upgraded to a write lock,in preference to other waiting readers or writers?

锁降级:从写锁变成读锁;锁升级:从读锁变成写锁。读锁是可以被多线程共享的,写锁是单线程独占的。也就是说写锁的并发限制比读锁高,这可能就是升级/降级名称的来源。

如下代码会产生死锁,因为同一个线程中,在没有释放读锁的情况下,就去申请写锁,这属于锁升级,ReentrantReadWriteLock是不支持的。

 ReadWriteLock rtLock = new ReentrantReadWriteLock();
 rtLock.readLock().lock();
 System.out.println("get readLock.");
 rtLock.writeLock().lock();
 System.out.println("blocking");

ReentrantReadWriteLock支持锁降级,如下代码不会产生死锁。

ReadWriteLock rtLock = new ReentrantReadWriteLock();
rtLock.writeLock().lock();
System.out.println("writeLock");

rtLock.readLock().lock();
System.out.println("get read lock");

这段代码虽然不会导致死锁,但没有正确的释放锁。从写锁降级成读锁,并不会自动释放当前线程获取的写锁,仍然需要显示的释放,否则别的线程永远也获取不到写锁。锁的释放和获取可以看下:可重入锁的获取和释放需要注意的一点儿事

final ReadWriteLock readWriteLock = new ReentrantReadWriteLock();
Thread wt = new Thread(new Runnable()
{

    @Override
    public void run()
    {
        readWriteLock.writeLock().lock();
        System.out.println("writeLock");

        readWriteLock.readLock().lock();
        System.out.println("readLock");
        readWriteLock.readLock().unlock();

        System.out.println("block");
    }
});

wt.start();

Thread.sleep(100);

System.out.println("main blocking.");
readWriteLock.readLock().lock();
<think>(Lock)在Java中是一种用于控制多个线程对共享资源进行访问的同步机制。在多线程环境下,可以防止多个线程同时访问共享资源,从而避免数据不一致竞态条件。Java提供了多种机制,包括内置(synchronized关键字)显式(java.util.concurrent.locks包中的)。 ### 1. 内置(synchronized) 内置是Java中最基本的机制,通过`synchronized`关键字实现。它可以用于同步方法或同步代码块。 #### 同步方法 ```java public synchronized void synchronizedMethod() { // 同步方法体,是当前对象实例(对于实例方法)或类对象(对于静态方法) } ``` #### 同步代码块 ```java public void method() { // 非同步代码 synchronized (lockObject) { // 同步代码块,是lockObject对象 } } ``` **特点**: - 内置是可重入的:同一个线程可以多次获取同一把。 - 自动释放:当同步块执行完毕或发生异常时,会自动释放。 - 不支持中断:线程在等待的过程中无法被中断。 - 不支持超时:无法设置获取的超时时间。 - 非公平:默认情况下,内置是非公平的,不保证等待线程的获取顺序。 ### 2. 显式(Lock接口) Java 5引入了`java.util.concurrent.locks`包,提供了更灵活的机制,主要实现是`ReentrantLock`。 #### 使用示例 ```java Lock lock = new ReentrantLock(); public void method() { lock.lock(); // 获取 try { // 同步代码块 } finally { lock.unlock(); // 必须在finally块中释放 } } ``` **显式的优势**: 1. **可中断**:`lockInterruptibly()`方法允许在等待的过程中响应中断。 2. **超时机制**:`tryLock(long time, TimeUnit unit)`可以设置超时时间。 3. **公平**:`ReentrantLock`可以设置为公平(按等待时间顺序获取)。 4. **条件变量**:通过`Condition`对象,可以实现更精细的线程等待唤醒。 #### 条件变量(Condition) ```java Lock lock = new ReentrantLock(); Condition condition = lock.newCondition(); public void await() throws InterruptedException { lock.lock(); try { condition.await(); // 释放并等待 } finally { lock.unlock(); } } public void signal() { lock.lock(); try { condition.signal(); // 唤醒一个等待线程 } finally { lock.unlock(); } } ``` ### 3. 读写锁ReadWriteLock) `ReadWriteLock`接口定义了一对:读(共享(排他)。适用于读多写少的场景。 #### 使用示例 ```java ReadWriteLock rwLock = new ReentrantReadWriteLock(); Lock readLock = rwLock.readLock(); Lock writeLock = rwLock.writeLock(); // 读操作 public void read() { readLock.lock(); try { // 读操作 } finally { readLock.unlock(); } } // 写操作 public void write() { writeLock.lock(); try { // 写操作 } finally { writeLock.unlock(); } } ``` **特点**: - 读是共享的:多个线程可以同时持有读。 - 写是排他的:同一时间只有一个线程可以持有写,且写获取时不能有读或写存在。 ### 4. 的最佳实践 - 总是使用`try-finally`确保被释放。 - 尽量减小同步代码块的范围,提高并发性能。 - 避免嵌套,防止死。 - 考虑使用`java.util.concurrent`包中的高级并发工具(如`Semaphore`, `CountDownLatch`等)。 ### 死及避免 死是指两个或更多线程互相持有对方所需的而无限等待的状态。死发生的四个必要条件: 1. 互斥条件 2. 请求与保持条件 3. 不可剥夺条件 4. 循环等待条件 **避免死的方法**: - 按顺序获取(所有线程以相同的顺序获取)。 - 使用超时机制(如`tryLock`)。 - 避免嵌套。 ### 总结 是Java多线程编程中同步访问共享资源的重要工具。内置(synchronized)简单易用,而显式(Lock)提供了更灵活高级的功能。根据具体场景选择合适的机制,并遵循最佳实践以避免死性能问题。 §§相关问题§§: 1. synchronizedReentrantLock有什么区别? 2. 什么是可重入? 3. 如何检测避免死? 4. 读写锁适用于什么场景?</think>### Java机制详解 是Java并发编程中控制多线程访问共享资源的核心同步机制,主要解决**线程安全****可见性**问题。Java提供了多种实现,可分为两大类: #### 一、内置(隐式) 通过`synchronized`关键字实现,JVM自动管理的获取释放。 **1. 同步方法** ```java public synchronized void syncMethod() { // 对象为当前实例(this) } ``` **2. 同步代码块** ```java public void method() { synchronized (lockObject) { // 显式指定对象 // 临界区代码 } } ``` **3. 静态同步** ```java public static synchronized void staticSync() { // 对象为Class对象(MyClass.class) } ``` **特性**: - 可重入性:线程可重复获取已持有的 - 互斥性:同一时刻仅一个线程可持有 - 非公平性:不保证等待线程的获取顺序 - 自动释放:代码块结束或异常时自动释放 #### 二、显式(`java.util.concurrent.locks`包) 提供更灵活的控制,核心接口`Lock`。 **1. ReentrantLock(可重入)** ```java Lock lock = new ReentrantLock(); void criticalSection() { lock.lock(); // 手动获取 try { // 临界区代码 } finally { lock.unlock(); // 必须手动释放 } } ``` **优势**: - 公平选项:`new ReentrantLock(true)` - 等待超时:`tryLock(long timeout, TimeUnit unit)` - 可中断:`lockInterruptibly()` - 条件变量支持:`Condition condition = lock.newCondition()` **2. ReadWriteLock读写锁)** ```java ReadWriteLock rwLock = new ReentrantReadWriteLock(); Lock readLock = rwLock.readLock(); Lock writeLock = rwLock.writeLock(); // 读操作(共享) void readData() { readLock.lock(); try { /* 并发读操作 */ } finally { readLock.unlock(); } } // 写操作(排他) void writeData() { writeLock.lock(); try { /* 独占写操作 */ } finally { writeLock.unlock(); } } ``` #### 三、特殊类型 1. **StampedLock(JDK8+)** 乐观读实现: ```java StampedLock sl = new StampedLock(); // 乐观读 long stamp = sl.tryOptimisticRead(); if (!sl.validate(stamp)) { // 检查是否被修改 stamp = sl.readLock(); // 升级为悲观读 } try { /* 读操作 */ } finally { sl.unlockRead(stamp); } ``` 2. **锁降级(写→读)** ```java writeLock.lock(); try { // 写操作... readLock.lock(); // 保持写时获取读 } finally { writeLock.unlock(); } try { /* 读操作 */ } finally { readLock.unlock(); } ``` #### 四、的内存语义 - **可见性保证**:释放前将本地内存刷新到主内存 - **Happens-Before原则**:解操作先于后续加操作 #### 五、优化技术 1. **消除**:JIT编译器移除不可能竞争的 2. **粗化**:合并相邻同步块减少开销 3. **偏向**:无竞争时消除同步开销 4. **适应性自旋**:动态调整自旋等待时间 #### 六、死预防 使用`tryLock`避免死: ```java Lock lockA = new ReentrantLock(); Lock lockB = new ReentrantLock(); void safeOperation() { while (true) { if (lockA.tryLock()) { try { if (lockB.tryLock()) { // 尝试获取第二把 try { /* 操作资源 */ } finally { lockB.unlock(); } } } finally { lockA.unlock(); } } Thread.sleep(10); // 避免活 } } ``` #### 七、的选择原则 | 场景 | 推荐类型 | |------|------------| | 简单同步 | `synchronized` | | 需要超时/中断 | `ReentrantLock` | | 读多写少 | `ReadWriteLock` | | 极高性能读 | `StampedLock` | | 无竞争同步 | `Atomic`原子类 | ### 总结 Java机制从`synchronized`到`StampedLock`经历了显著的演进,核心目标是平衡**线程安全**与**性能开销**。正确选择类型需考虑:竞争强度、读写比例、是否需要公平性等。始终遵循"最小化临界区"原则,并优先考虑`java.util.concurrent`包中的高级并发工具类。
评论 1
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值