10. 读写锁的思想

读锁:

读锁拒绝其他线程获得写锁,读锁不拒绝其他线程获得读锁,多个上了读锁的线程可以并发读不会阻塞。
多个读锁同时作用期间,其他想上写锁的线程都处在等待状态,当最后一个读锁释放后,才有可能上锁。

写锁:

写锁拒绝其他线程获取读锁和写锁。
当一个线程获取写锁后,其他想要获取读写锁的线程都处于等待状态,直到写锁释放才有可能上锁。

代码实例

直接拿Java官方示例解读了。

class CachedData {
   Object data;
   volatile boolean cacheValid;
   final ReentrantReadWriteLock rwl = new ReentrantReadWriteLock();
   void processCachedData() {
     rwl.readLock().lock();  //1. 上读锁
     if (!cacheValid) {      //2. 验证cacheValid
        // Must release read lock before acquiring write lock
        rwl.readLock().unlock(); //3. 解除读锁
        rwl.writeLock().lock(); //4. 上写锁
        try {
          // Recheck state because another thread might have
          // acquired write lock and changed state before we did.
          if (!cacheValid) {    //5. 验证cacheValid
            data = ...
            cacheValid = true;
          }
          // Downgrade by acquiring read lock before releasing write lock
          rwl.readLock().lock(); //6. 上读锁
        } finally {
          rwl.writeLock().unlock(); // Unlock write, still hold read //7. 解除写锁
        }
     }
     try {
       use(data);
     } finally {
       rwl.readLock().unlock();//8. 解除读锁
     }
   }
 }

复制代码比如现在有线程ABCDE五个线程,使用processCachedData()方法,接下来会发现如下步骤。
-> DE线程滞后,ABC同时进入到步骤1. 上读锁-> ABC进入到步骤2. 验证cacheValid。(新实例中cacheValid初始为fasle,所以进入if条件句中)-> ABC执行步骤3. 解除读锁。-> 假设此时A率先完成步骤4. 上写锁。-> BC无法获取写锁,处于等待状态,被阻塞在步骤4。 上写锁。此时D线程执行使用processCachedData()方法,被阻塞在步骤1. 上读锁。-> A进入到步骤5. 验证cacheValid。步骤五很关键,如果线程A写完后,解除了写锁,此时新的线程E获取到了写锁,就会写入新数据,此时就不是同步锁了,程序出错。 -> A修改数据,cacheValid置为true。步骤6和步骤7是写锁的降级操作,即写锁释放的时候,先降级为读锁,这样其他等待获取写锁的线程会继续等待,然后再释放写锁,保证同步性。-> A执行完步骤8,BC阻塞结束,其中一位获取写锁。

读写锁的实现原理分析

读写状态的设计

读写锁同样依赖自定义同步器来实现同步功能,而读写状态就是其同步器的同步状态。回想ReentrantLock中自定义同步器的实现,同步状态表示锁被一个线程重复获取的次数,而读写锁的自定义同步器需要在同步状态(一个整型变量)上维护多个读线程和一个写线程的状
态,使得该状态的设计成为读写锁实现的关键。如果在一个整型变量上维护多种状态,就一定需要“按位切割使用”这个变量,读写锁将变量切分成了两个部分,高16位表示读,低16位表示写,划分方式如下图所示


image.png

当前同步状态表示一个线程已经获取了写锁,且重进入了两次,同时也连续获取了两次读锁。读写锁是如何迅速确定读和写各自的状态呢?答案是通过位运算。假设当前同步状态值为S,写状态等于S&0x0000FFFF(将高16位全部抹去),读状态等于S>>>16(无符号补0右移16位)。当写状态增加1时,等于S+1,当读状态增加1时,等于S+(1<<16),也就是S+0x00010000。根据状态的划分能得出一个推论:S不等于0时,当写状态(S&0x0000FFFF)等于0时,则读状态(S>>>16)大于0,即读锁已被获取

你可能感兴趣的:(10. 读写锁的思想)