读写锁之ReentranReadWriteLock

125 阅读2分钟

携手创作,共同成长!这是我参与「掘金日新计划 · 8 月更文挑战」的第23天,点击查看活动详情

1ReentranReadWriteLock

当读操作远远高于写操作时,这时候使用 读写锁 让 读-读 可以并发,提高性能.

如存在一个数据容器类内部分别使用读锁保护数据的 read() 方法,写锁保护数据的 write() 方法.

class DataContainer {
     private Object data;
     private ReentrantReadWriteLock rw = new ReentrantReadWriteLock();
     private ReentrantReadWriteLock.ReadLock r = rw.readLock();
     private ReentrantReadWriteLock.WriteLock w = rw.writeLock();
    
    public Object read() {
        log.debug("获取读锁...");
        r.lock();
        try {
            log.debug("读取");
            sleep(1);
            return data;
        } finally {
            log.debug("释放读锁...");
            r.unlock();
        }
    }
    
     public void write() {
         log.debug("获取写锁...");
         w.lock();
         try {
             log.debug("写入");
             sleep(1);
         } finally {
             log.debug("释放写锁...");
             w.unlock();
         }
     }
}

读读-测试

DataContainer dataContainer = new DataContainer();
new Thread(() -> {
     dataContainer.read();
}, "t1").start();

new Thread(() -> {
     dataContainer.read();
}, "t2").start();

运行结果:

07:05:14.341 c.DataContainer [t2] - 获取读锁... 
07:05:14.341 c.DataContainer [t1] - 获取读锁... 
07:05:14.345 c.DataContainer [t1] - 读取
07:05:14.345 c.DataContainer [t2] - 读取
07:05:15.365 c.DataContainer [t2] - 释放读锁... 
07:05:15.386 c.DataContainer [t1] - 释放读锁...

从结果看,线程读没有互相影响.

读写-测试

DataContainer dataContainer = new DataContainer();
new Thread(() -> {
     dataContainer.read();
}, "t1").start();
Thread.sleep(100);
new Thread(() -> {
     dataContainer.write();
}, "t2").start();

运行结果:

08:04:21.838 c.DataContainer [t1] - 获取读锁... 
08:04:21.838 c.DataContainer [t2] - 获取写锁... 
08:04:21.841 c.DataContainer [t2] - 写入
08:04:22.843 c.DataContainer [t2] - 释放写锁... 
08:04:22.843 c.DataContainer [t1] - 读取
08:04:23.843 c.DataContainer [t1] - 释放读锁... 

从结果看,线程读写是互相阻塞的.

注意

  • 读锁不支持条件变量
  • 重入时升级不支持:即持有读锁的情况下去获取写锁,会导致获取写锁永久等待

案例:

    r.lock();
try {
     // ...
     w.lock();
 try {
     // ...
 } finally{
     w.unlock();
 }
} finally{
     r.unlock();
}
  • 重入时降级支持:即持有写锁的情况下去获取读锁

案例

class CachedData {
     Object data;
     // 是否有效,如果失效,需要重新计算 data
     volatile boolean cacheValid;
     final ReentrantReadWriteLock rwl = new ReentrantReadWriteLock();
     void processCachedData() {
     rwl.readLock().lock();
         
     if (!cacheValid) {
         // 获取写锁前必须释放读锁
         rwl.readLock().unlock();
         rwl.writeLock().lock();
         try {
             // 判断是否有其它线程已经获取了写锁、更新了缓存, 避免重复更新
             if (!cacheValid) {
                 data = ...
                 cacheValid = true;
             }
             // 降级为读锁, 释放写锁, 这样能够让其它线程读取缓存
             rwl.readLock().lock();
         } finally {
              rwl.writeLock().unlock();
         }
     }
     // 自己用完数据, 释放读锁 
     try {
         use(data);
     } finally {
         rwl.readLock().unlock();
     }
 }
}