Java 读写锁的深度理解

一、什么是读写锁

一对关联的锁,一个用于只读操作,一个用于写入
读锁可以由多个线程同时持有,写锁是排他的

二、哪些场景适用

适合读取线程比写入线程多的场景,改进互斥锁的性能
比如:缓存组件、集合的并发线程安全性改造

三、读写锁的一些细节注意的地方

  1. 加了读锁之后,如果未释放,则无法加写锁;必须先释放读锁,才能加写锁
  2. 而且必须是所有线程都释放了读锁,其他线程才能抢到写锁。

四、JDK ReentrantReadWriteLock 源码中使用读写锁实现缓存的代码理解

源码(在类的注释上)如下:

class CachedData {
   Object data;
   volatile boolean cacheValid;
   final ReentrantReadWriteLock rwl = new ReentrantReadWriteLock();

   void processCachedData() {
     rwl.readLock().lock();
     if (!cacheValid) {
       // Must release read lock before acquiring write lock
       rwl.readLock().unlock();
       rwl.writeLock().lock();
       try {
         // Recheck state because another thread might have
         // acquired write lock and changed state before we did.
         if (!cacheValid) {
           data = ...
           cacheValid = true;
         }
         // Downgrade by acquiring read lock before releasing write lock
         rwl.readLock().lock();
       } finally {
         rwl.writeLock().unlock(); // Unlock write, still hold read
       }
     }

     try {
       use(data);
     } finally {
       rwl.readLock().unlock();
     }
   }
 }

解释
首先这个程序在实现一个什么功能?
类似于我们平时使用的缓存,如果缓存数据中存在,则直接从缓存中把数据取出来使用;如果缓存中数据不存在,则从数据库中查询数据,并写入到缓存中。

(假设现在有一千个线程在并发运行)

细节
第 7 行,先加一把读锁,保证在使用的时候,其他线程不能写入。并且可以多线程来读。
第 8 行,判断缓存是否失效(或者数据是否存在)。这里为什么要判断不存在才去查询数据库?不能直接查询数据库吗?
因为大量的线程执行到第 8 行,都发现缓存不存在,都会去查询数据库,就会造成数据库压力增大(缓存雪崩),此时我们会先加一个写锁,只有一个线程能获得到这个锁。
第 11 行,加写锁。写锁只能有一个线程获得。避免所有线程都去查询数据库。拿不到锁的线程(999个线程)在此等待。

第 15 行,再判断一次数据是否存在(双重检查)。如果线程查到了数据,则其他 999 个线程,判断一下发现数据有了就直接返回了。否则 999 个线程还是会去数据库查数据。

第 20 行,加读锁,称为锁降级(写锁未释放的情况下,获得到读锁,随后再释放写锁的过程)。这是为了在数据查询完到返回这个过程,不允许其他线程修改这个值。

第 22 行,释放写锁。但仍然有线程拿着这个读锁,其他线程无法拿到写锁修改这个值。

第 27 行,使用数据。此时仍然被加了读锁,数据无法修改。

第 29 行,释放读锁。

你可能感兴趣的:(Java)