读写锁的锁降级:只降不升🔽

31 阅读2分钟

锁降级:从写锁降到读锁,安全!锁升级:从读锁升到写锁,死锁!

一、什么是锁降级?

锁降级(Downgrade)✅

ReadWriteLock lock = new ReentrantReadWriteLock();

lock.writeLock().lock();    // 1. 获取写锁
try {
    // 修改数据
    data = newData;
    
    lock.readLock().lock(); // 2. 获取读锁(降级开始)
} finally {
    lock.writeLock().unlock(); // 3. 释放写锁(降级完成)
}

try {
    // 4. 持有读锁,读取数据
    return data;
} finally {
    lock.readLock().unlock();  // 5. 释放读锁
}

顺序: 写锁 → 读锁 → 释放写锁 = 降级成功

二、为什么不能锁升级?

锁升级(Upgrade)❌会死锁

lock.readLock().lock();     // 1. 获取读锁
try {
    // 读取数据
    
    // 尝试升级
    lock.writeLock().lock(); // 2. 💣 死锁!
    try {
        // 永远执行不到
    } finally {
        lock.writeLock().unlock();
    }
} finally {
    lock.readLock().unlock();
}

原因:

线程A持有读锁 → 请求写锁 → 等待读锁释放
线程B持有读锁 → 也请求写锁 → 等待读锁释放
所有读锁都在等待写锁 → 死锁!

三、完整的锁降级示例

缓存更新场景

public class CachedData {
    private Object data;
    private volatile boolean cacheValid;
    private final ReadWriteLock rwl = new ReentrantReadWriteLock();
    
    public Object getData() {
        rwl.readLock().lock();  // 获取读锁
        
        if (!cacheValid) {
            // 缓存失效,需要更新
            rwl.readLock().unlock();  // 释放读锁
            
            rwl.writeLock().lock();   // 获取写锁
            try {
                // 再次检查(可能有其他线程已更新)
                if (!cacheValid) {
                    data = loadData();  // 加载数据
                    cacheValid = true;
                }
                
                // ✅ 锁降级:获取读锁
                rwl.readLock().lock();
                
            } finally {
                rwl.writeLock().unlock();  // 释放写锁(降级完成)
            }
        }
        
        try {
            return data;  // 持有读锁返回数据
        } finally {
            rwl.readLock().unlock();
        }
    }
    
    private Object loadData() {
        // 从数据库或远程加载
        return new Object();
    }
}

四、锁降级的好处

不降级版本

public Object getData() {
    rwl.writeLock().lock();
    try {
        if (!cacheValid) {
            data = loadData();
            cacheValid = true;
        }
        return data;
    } finally {
        rwl.writeLock().unlock();
    }
}
// ❌ 问题:读也用写锁,性能差

降级版本

// ✅ 写锁 → 读锁
// 好处:数据更新后,其他线程可以并发读取

五、StampedLock的锁转换

支持锁升级(特殊)

StampedLock lock = new StampedLock();

long stamp = lock.readLock();  // 读锁
try {
    // 需要写
    long ws = lock.tryConvertToWriteLock(stamp);  // 尝试升级
    
    if (ws != 0L) {
        stamp = ws;  // 升级成功
        // 修改数据
    } else {
        // 升级失败,先释放读锁,再获取写锁
        lock.unlockRead(stamp);
        stamp = lock.writeLock();
        // 修改数据
    }
} finally {
    lock.unlock(stamp);
}

六、对比表

特性ReentrantReadWriteLockStampedLock
锁降级✅ 支持✅ 支持
锁升级❌ 死锁⚠️ 尝试转换
可重入✅ 支持❌ 不支持
性能⭐⭐⭐⭐⭐⭐⭐⭐

七、面试高频问答💯

Q: 为什么支持锁降级但不支持升级?

A:

  • 降级:写锁是独占的,降级为读锁不影响其他线程
  • 升级:读锁是共享的,升级需要等待其他读锁释放,容易死锁

Q: 锁降级的步骤是什么?

A:

  1. 持有写锁
  2. 获取读锁
  3. 释放写锁
  4. 持有读锁

Q: 如何实现类似锁升级的效果?

A: 先释放读锁,再获取写锁(但要重新检查条件)。


下一篇→ SynchronousQueue:零容量的神奇队列🎭