跳至主要內容

读写锁

zheng大约 5 分钟java基础

4、读写锁

1、读写锁介绍:

    ReadWriteLock同Lock一样也是一个接口,提供了readLock和writeLock两种锁的操作机制,一个是只读的锁,一个是写锁。 

    理论上,读写锁比互斥锁允许对于共享数据更大程度的并发。与互斥锁相比,读写锁是否能够提高性能取决于读写数据的频率、读取和写入操作的持续时间、以及读线程和写线程之间的竞争。 

    一些业务场景中,大部分 只是读数据,写数据很少,如果仅仅是读数据的话并不会影响数据正确性(出现脏读),而如果在这种业务场景下,依然使用独占锁的话,很显然这将是出现性能瓶颈的地方。 针对这种读多写少的情况,java还提供了另外一个实现Lock接口的ReentrantReadWriteLock(读写锁)。读写所允许同一时刻被多个读线程访问,但是在写线程访问时,所有的读线程和其他的写线程都会被阻塞。



    读-读能共存,
     读-写不能共存,
     写-写不能共存。 

连接:https://blog.csdn.net/j080624/article/details/82790372、https://ifeve.com/read-write-locks/open in new window

2、总结:

  1. 公平性选择:支持非公平性(默认)和公平的锁获取方式,吞吐量还是非公平优于公平;
  2. 重入性:支持重入,读锁获取后能再次获取,写锁获取之后能够再次获取写锁,同时也能够获取读锁;
  3. 锁降级:遵循获取写锁,获取读锁再释放写锁的次序,写锁能够降级成为读锁

3、写锁的获取:

    写锁是独占式锁,而实现写锁的同步语义是通过重写 AQS 中的 tryAcquire() 方法实现的,源码:
protected final boolean tryAcquire(int acquires) {
     Thread current = Thread.currentThread();
     // 1. 获取 写锁 当前的同步状态
     int c = getState();
     // 2. 获取 写锁 获取的次数
     int w = exclusiveCount(c);
     if (c != 0) {
         // (Note: if c != 0 and w == 0 then shared count != 0)
         // 3.1 当 读锁 已被读线程获取 或者 当前线程不是已经获取 写锁 的线程的话
         // 当前线程获取 写锁失败
         if (w == 0 || current != getExclusiveOwnerThread())
             return false;
         if (w + exclusiveCount(acquires) > MAX_COUNT)
             throw new Error("Maximum lock count exceeded");
         // Reentrant acquire
         // 3.2 当前线程 获取写锁,支持可重复加锁
         setState(c + acquires);
         return true;
     }
     // 3.3 写锁 未被任何线程获取,当前线程可获取 写锁
     if (writerShouldBlock() ||!compareAndSetState(c, c + acquires))
         return false;
     setExclusiveOwnerThread(current);
     return true;
 }

 

 static int exclusiveCount(int c) { 

        return c & EXCLUSIVE_MASK;

 }

其中EXCLUSIVE_MASK为: static final int EXCLUSIVE_MASK = (1 << SHARED_SHIFT) - 1; EXCLUSIVE _MASK为1左移16位然后减1,即为0x0000FFFF。

而exclusiveCount方法是将同步状态(state为int类型)与0x0000FFFF相与,即取同步状态的低16位。那么低16位代表什么呢?

根据exclusiveCount方法的注释为独占式获取的次数即写锁被获取的次数,现在就可以得出来一个结论同步状态的低16位用来表示写锁的获取次数

static int sharedCount(int c)    { 

        return c >>> SHARED_SHIFT; 

}

该方法是获取读锁被获取的次数,是将同步状态(int c)右移16次,即取同步状态的高16位,现在我们可以得出另外一个结论同步状态的高16位用来表示读锁被获取的次数。

img
img

当读锁已经被读线程获取或者写锁已经被其他写线程获取,则写锁获取失败;否则,获取成功并支持重入,增加写状态。

4、写锁的释放:

写锁释放通过重写AQS的tryRelease方法,源码为:
protected final boolean tryRelease(int releases) {
     if (!isHeldExclusively())
         throw new IllegalMonitorStateException();
     //1. 同步状态减去写状态
     int nextc = getState() - releases;
     //2. 当前写状态是否为0,为0则释放写锁
     boolean free = exclusiveCount(nextc) == 0;
     if (free)
         setExclusiveOwnerThread(null);
     //3. 不为0则更新同步状态
     setState(nextc);
     return free;
 }
减少写状态int nextc = getState() - releases,只需要用当前同步状态直接减去写状态的原因:写状态是由同步状态的低16位表示的。

5、读锁的获取

    读锁不是独占式锁,即同一时刻该锁可以被多个读线程获取也就是一种共享式锁。
protected final int tryAcquireShared(int unused) {
     Thread current = Thread.currentThread();
     int c = getState();
     //1. 如果写锁已经被获取并且获取写锁的线程不是当前线程的话,当前
     // 线程获取读锁失败返回-1
     if (exclusiveCount(c) != 0 &&
         getExclusiveOwnerThread() != current)
         return -1;
     int r = sharedCount(c);
     if (!readerShouldBlock() &&
         r < MAX_COUNT &&
         //2. 当前线程获取读锁
         compareAndSetState(c, c + SHARED_UNIT)) {
         //3. 下面的代码主要是新增的一些功能,比如getReadHoldCount()方法
         //返回当前获取读锁的次数
         if (r == 0) {
             firstReader = current;
             firstReaderHoldCount = 1;
         } else if (firstReader == current) {
             firstReaderHoldCount++;
         } else {
             HoldCounter rh = cachedHoldCounter;
             if (rh == null || rh.tid != getThreadId(current))
                 cachedHoldCounter = rh = readHolds.get();
             else if (rh.count == 0)
                 readHolds.set(rh);
             rh.count++;
         }
         return 1;
     }
     //4. 处理在第二步中CAS操作失败的自旋已经实现重入性
     return fullTryAcquireShared(current);
 }
当写锁被其他线程获取后,读锁获取失败,否则获取成功利用CAS更新同步状态。

6、读锁的释放

protected final boolean tryReleaseShared(int unused) {
     Thread current = Thread.currentThread();
     // 前面还是为了实现getReadHoldCount等新功能
     if (firstReader == current) {
         // assert firstReaderHoldCount > 0;
         if (firstReaderHoldCount == 1)
             firstReader = null;
         else
             firstReaderHoldCount--;
     } else {
         HoldCounter rh = cachedHoldCounter;
         if (rh == null || rh.tid != getThreadId(current))
             rh = readHolds.get();
         int count = rh.count;
         if (count <= 1) {
             readHolds.remove();
             if (count <= 0)
                 throw unmatchedUnlockException();
         }
         --rh.count;
     }     for (;;) {
         int c = getState();
         // 读锁释放 将同步状态减去读状态即可
         int nextc = c - SHARED_UNIT;
         if (compareAndSetState(c, nextc))
             // Releasing the read lock has no effect on readers,
             // but it may allow waiting writers to proceed if
             // both read and write locks are now free.
             return nextc == 0;
     }
 }

7、锁降级

    读写锁支持锁降级,遵循按照获取写锁,获取读锁再释放写锁的次序,写锁能够降级成为读锁,不支持锁升级,关于锁降级下面的示例代码摘自ReentrantWriteReadLock源码中:
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();
       }
     }
 }
上次编辑于:
贡献者: 郑天祺