前言 独占锁(排它锁)在同一时刻只允许一个线程进行访问,如 并发 - ReentrantLock 一文中介绍的 ReentrantLock
就是一个独占锁。而读写锁在同一时刻可以允许多个读线程 访问,但是在写线程访问时,所有的读线程和其它写线程都会被阻塞。读写锁维护了一对锁,一个读锁和一个写锁,通过分离读锁和写锁,不仅保证了写操作对读操作的可见性,还使得并发性相比一般的排它锁有更大提升 。在读多写少的情况下,读写锁能够提供比排它锁更好的并发性和吞吐量。
场景 读写锁除了保证写操作对读操作的可见性以及并发性的提升之外,读写锁还能够简化读写交互场景的编码实现 。对于一个共享的缓存数据,一般都是读多写少,但是写操作完成之后的更新需要对后续的读操作可见,这样做的目的是使读操作能读取到正确的数据。使用读写锁实现这样的功能,只需要在读操作时获取读锁,写操作时获取写锁即可。当写锁被获取时,后续其它线程的读写操作都会被阻塞,写锁释放之后,所有操作才会继续执行。相比于使用等待-通知机制 ,更加简单化。
概述 Java 并发包提供读写锁的实现是 ReentrantReadWriteLock ,它支持以下特性:
公平性选择: 支持非公平(默认)和公平的锁获取方式,吞吐量还是非公平优于公平
重入性: 支持锁重入,包括读锁和写锁
锁降级: 同一个线程获取的写锁能够降级为读锁,反之不行。遵循获取写锁、获取读锁、释放写锁、释放读锁
读写状态 读写锁 ReentrantReadWriteLock
同样是基于 AQS 实现的锁功能,而读写状态就是 AQS 的同步状态。在 ReentrantLock
中同步状态表示锁被一个线程持有的次数,而读写锁需要在同步状态上维护多个读线程和一个写线程的信息,这就使得同步状态的设计成为读写锁实现的关键 。由于同步状态 state
是一个整型变量,4 个字节 32 位,因此读写锁将该变量切分成了两部分,高 16 位表示读,低 16 位表示写,划分方式如下图所示:
上图中的同步状态表示一个线程已经获取了写锁且重入了 2 次,同时也连续获取了两次读锁。读写锁是通过位运算来确定读和写各自的状态的 。下面对状态的变化过程进行说明。
假设当前同步状态 state 的值为 S
获取写状态
S & (1 << 16 -1) -> 将高16位全部抹去
获取读状态
S>>>16 -> 无符号补0,右移16位
更新操作
写状态增加 1 时 -> S + 1 读状态增加 1 时 -> S + (1<<16),也就是 S + 0x00010000
注意: 读写锁 ReentrantReadWriteLock
虽然使用同步状态 state
的高低位来表示读写状态,但是同步队列依然是共用一个。
源码分析 ReentrantReadWriteLock 的类继承关系类图如下:
由继承关系图可知,读写锁 ReentrantReadWriteLock 是通过内部类 Sync 继承 AQS 来行使同步器的职能。由于该读写锁支持公平和非公平模式,因此通过继承内部类 Sync 的方式定义了非公平模式的 NonfairSync 和公平模式的 FairSync 。读写锁的实现依赖组合的 Sync ,也就是说 ReadLock 和 WriteLock 获取和释放锁的功能是交给 Sync 去实现的,公平模式下使用 FairSync ,非公平模式下使用 NonfairSync 。
读写锁 ReentrantReadWriteLock 组合关系如下图所示:
ReentrantReadWriteLock 分为读锁 ReadLock 和写锁 WriteLock 。读锁是共享锁,可被多个线程同时占有;写锁是独占锁,同时只能有一个线程占有,且写锁被线程占有后其它线程既不能获取读锁也不能获取写锁,但占有写锁的线程可以在不释放写锁的情况下继续获取读锁,这是锁降级的特点。
代码结构 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 public class ReentrantReadWriteLock implements ReadWriteLock , java .io .Serializable { private static final long serialVersionUID = -6992448646407690164L ; private final ReentrantReadWriteLock.ReadLock readerLock; private final ReentrantReadWriteLock.WriteLock writerLock; public ReentrantReadWriteLock.WriteLock writeLock () { return writerLock; } public ReentrantReadWriteLock.ReadLock readLock () { return readerLock; } final Sync sync; public ReentrantReadWriteLock () { this (false ); } public ReentrantReadWriteLock (boolean fair) { sync = fair ? new FairSync() : new NonfairSync(); readerLock = new ReadLock(this ); writerLock = new WriteLock(this ); } abstract static class Sync extends AbstractQueuedSynchronizer {...} static final class NonfairSync extends Sync {...} static final class FairSync extends Sync {...} public static class ReadLock implements Lock , java .io .Serializable { private static final long serialVersionUID = -5992448646407690164L ; private final Sync sync; protected ReadLock (ReentrantReadWriteLock lock) { sync = lock.sync; } } public static class WriteLock implements Lock , java .io .Serializable { private static final long serialVersionUID = -4992448646407690164L ; private final Sync sync; protected WriteLock (ReentrantReadWriteLock lock) { sync = lock.sync; } }
通过前文分析的 ReentrantReadWriteLock
UML 类图和相关的组合关系图,不难发现与上述代码结构是一一对应的。下面我们依次对读锁和写锁依赖的 AQS 相关实现进行介绍,理解了相关的实现后也就基本理解了读写锁的实现。
Sync 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 abstract static class Sync extends AbstractQueuedSynchronizer { private static final long serialVersionUID = 6317671515068378041L ; static final int SHARED_SHIFT = 16 ; static final int SHARED_UNIT = (1 << SHARED_SHIFT); static final int MAX_COUNT = (1 << SHARED_SHIFT) - 1 ; static final int EXCLUSIVE_MASK = (1 << SHARED_SHIFT) - 1 ; static int sharedCount (int c) { return c >>> SHARED_SHIFT; } static int exclusiveCount (int c) { return c & EXCLUSIVE_MASK; } static final class HoldCounter { int count = 0 ; final long tid = getThreadId(Thread.currentThread()); } static final class ThreadLocalHoldCounter extends ThreadLocal <HoldCounter > { @Override public HoldCounter initialValue () { return new HoldCounter(); } } private transient ThreadLocalHoldCounter readHolds; private transient HoldCounter cachedHoldCounter; private transient Thread firstReader = null ; private transient int firstReaderHoldCount; Sync() { readHolds = new ThreadLocalHoldCounter(); setState(getState()); } abstract boolean readerShouldBlock () ; abstract boolean writerShouldBlock () ; }
继承 AQS 的静态内部类 Sync 负责读锁 ReadLock 和写锁 WriteLock 的获取与释放工作,对读写锁 ReentrantReadWriteLock 的公平和非公平支持交给了两个子类实现。下面对 Sync 中的属性和抽象方法进行介绍,这些属作为最基础的数据支持读写锁的运行与统计。
同步状态
读写锁将 int 类型的同步状态 state 同时赋予两种语义,高 16 位表示读锁的持有次数,包括线程重入锁的情况。获取到读锁一次:state + (1<<16)
,释放掉读锁一次:state - (1<<16)
;
低 16 位表示写锁的获取次数,因为写锁是独占锁,同时只能被一个线程获取,因此它代表的重入次数。获取写锁一次:state + 1
,释放写锁一次:state -1
。
线程读锁计数器
每个线程都需要记录获取的读锁次数,这样才能知道到底是不是读锁重入。注意,判断读锁重入和写锁重入完全不一样。写锁属于独占锁,同一时刻写锁只能一个线程持有,因此同步状态的低 16 位的值就是该线程持有写锁的次数(包括重入);读锁属于共享锁,同一时刻允许多个线程持有,而同步状态的高 16 位的值是所有线程持有的总次数(包括各个线程重入),因此不能借助同步状态得出各个读线程持有读锁的次数,也就不能判断是否读锁重入,因此需要线程读锁计数器来辅助完成该诉求。
读写锁使用 ThreadLocal 维护每个线程读锁计数器,这样就能识别出哪个线程持有多少次读锁,进而可以判断线程是否是读锁重入以及线程持有读锁的次数。此外,读写锁基于性能考虑,又引入 “首个线程读锁计数器” 和 “最后线程读锁计数器” 。其实 ThreadLocal<HoldCounter> readHolds
完全可以完成计数,只是 ThreadLocal 内部基于 Map 来查询的,相比直接使用变量记录线程读锁计数信息性能要差了那么一丢丢,不过这两个计数器只能记录一个线程持读锁信息,并且是动态变化的,提升性能的依据是尽可能先用这两个计数器,然后才使用通用的 ThreadLocal<HoldCounter> readHolds
记录线程读锁信息。
“首个线程读锁计数器” 是使用 firstReader 和 firstReaderHoldCount 两个属性组合而成的。“最后线程读锁计数器” 是使用 HoldCounter
类型的 cachedHoldCounter 属性表示。
读写公平策略
读写锁 ReentrantReadWriteLock 具体分为读锁 ReadLock 和写锁 WriteLock ,在公平和非公平模式下读锁和写锁的表现不同,因此将具体的实现交给公平和非公平子类实现。
非公平 Sync 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 +--- ReentrantReadWriteLock static final class NonfairSync extends Sync { private static final long serialVersionUID = -8159625535654395037L ; final boolean writerShouldBlock () { return false ; } final boolean readerShouldBlock () { return apparentlyFirstQueuedIsExclusive(); } }
在非公平模式下,写锁优先尝试抢占锁,抢占失败才会去排队;一般来说,非公平模式下读锁也应该直接尝试抢占锁,但是写锁被定义了更高的优先级 ,读锁会先判断队列中等待的第一个线程节点是否是获取写锁的,如果是就算是非公平模式也先让该节点获取写锁,避免线程饥饿。
公平 Sync 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 +--- ReentrantReadWriteLock static final class FairSync extends Sync { private static final long serialVersionUID = -2274990926593161451L ; final boolean writerShouldBlock () { return hasQueuedPredecessors(); } final boolean readerShouldBlock () { return hasQueuedPredecessors(); } }
在公平模式下,无论是写锁还是读锁,都遵循先来后到原则。需要说明的是,对于读锁的获取,无论是公平还是非公平模式,它都没有抢占的概念,即使是在非公平模式下,还是需要判断同步队列中的第一个线程节点是否是写线程 。
至此,读锁和写锁的前置准备已经完成,下面我们进入到读锁和写锁的源码。
读锁 读锁内部持有 ReentrantReadWriteLock
中的 Sync 类型的对象,可能是 FairSync
对象,也可能是 NonfairSync
对象,具体由 ReentrantReadWriteLock
构造函数决定。ReadLock 锁获取与释放功能全部委托给 sync 对象完成。
属性 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 +--- ReentrantReadWriteLock public static class ReadLock implements Lock , java .io .Serializable { private static final long serialVersionUID = -5992448646407690164L ; private final Sync sync; protected ReadLock (ReentrantReadWriteLock lock) { sync = lock.sync; } }
获取读锁 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 +--- ReadLock public void lock () { sync.acquireShared(1 ); } +--- AQS public final void acquireShared (int arg) { if (tryAcquireShared(arg) < 0 ) doAcquireShared(arg); } +--- Sync protected final int tryAcquireShared (int unused) { Thread current = Thread.currentThread(); int c = getState(); if (exclusiveCount(c) != 0 && getExclusiveOwnerThread() != current) return -1 ; int r = sharedCount(c); if (!readerShouldBlock() && r < MAX_COUNT && compareAndSetState(c, c + SHARED_UNIT)) { if (r == 0 ) { firstReader = current; firstReaderHoldCount = 1 ; } else if (firstReader == current) { firstReaderHoldCount++; } else { Sync.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 ; } return fullTryAcquireShared(current); }
读锁获取使用 AQS 的共享模式获取同步状态,整个流程如下:
判断写锁是否被其它线程占有(支持锁降级获取读锁),如果被其它线程占有直接获取读锁失败。
根据具体的公平或非公平模式判断获取读锁是否需要阻塞,阻塞的话会进入后续二次确认方法,即判断是否是重入获取读锁,重入获取读锁不需要阻塞。
获取读锁成功后,记录线程读锁计数器。
获取读锁的注意事项如下:
获取读锁前提条件是写锁没有被其它线程持有,当前线程持有写锁是可以继续获取读锁的,这是读写锁的锁降级特性。
在公平模式下,获取读锁时同步队列中有等待的线程节点,如果此时不是重入获取读锁,那么获取锁失败。
在非公平模式下,获取读锁时同步队列中第一个线程节点是获取写锁的情况,此时如果不是重入获取读锁,那么获取锁失败。写锁被定义更高的优先级。
获取锁成功后,需要记录当前线程读锁计数器。线程读锁计数器有两个作用,一个是用于判断线程是否是重入读锁,另一个是提供当前线程获取读锁的次数 。
fullTryAcquireShared 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 +--- Sync final int fullTryAcquireShared (Thread current) { Sync.HoldCounter rh = null ; for (; ; ) { int c = getState(); if (exclusiveCount(c) != 0 ) { if (getExclusiveOwnerThread() != current) return -1 ; } else if (readerShouldBlock()) { if (firstReader == current) { } else { if (rh == null ) { rh = cachedHoldCounter; if (rh == null || rh.tid != getThreadId(current)) { rh = readHolds.get(); if (rh.count == 0 ) readHolds.remove(); } } if (rh.count == 0 ) return -1 ; } } if (sharedCount(c) == MAX_COUNT) throw new Error("Maximum lock count exceeded" ); if (compareAndSetState(c, c + SHARED_UNIT)) { if (sharedCount(c) == 0 ) { firstReader = current; firstReaderHoldCount = 1 ; } else if (firstReader == current) { firstReaderHoldCount++; } else { if (rh == null ) rh = cachedHoldCounter; if (rh == null || rh.tid != getThreadId(current)) rh = readHolds.get(); else if (rh.count == 0 ) readHolds.set(rh); rh.count++; cachedHoldCounter = rh; } return 1 ; } } }
上述方法在一定程度上是对 tryAcquireShared
方法的冗余,主要是对并发获取读锁失败以及重入获取锁的处理。具体作用如下:
tryAcquireShared 方法中 CAS 获取同步状态失败后增加获取读锁成功的机会,尽可能不进入同步队列。
处理 tryAcquireShared 中因获取读锁需要阻塞的情况(上述方法只会处理重入读锁的情况,因为重入读锁不需要阻塞,非重入就需要阻塞,也就是获取读锁再次失败)
在非公平模式 NonFairSync 情况下,如果同步队列中 head.next 是获取写锁的节点,那么如果该线程不是重入读锁则获取失败,如果是重入读锁则获取成功,因为重入优先级更高。
在公平模式 FairSync 情况下,如果同步队列中有线程节点等待,那么如果不是重入读锁则获取失败,如果是重入读锁则获取成功,同样地,因为重入优先级更高。
释放读锁 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 +--- ReadLock public void unlock () { sync.releaseShared(1 ); } +--- AQS public final boolean releaseShared (int arg) { if (tryReleaseShared(arg)) { doReleaseShared(); return true ; } return false ; } +--- Sync protected final boolean tryReleaseShared (int unused) { Thread current = Thread.currentThread(); if (firstReader == current) { if (firstReaderHoldCount == 1 ) firstReader = null ; else firstReaderHoldCount--; } else { Sync.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)) return nextc == 0 ; } }
读锁释放过程比较简单,主要还是对应的两个操作,具体如下:
更新当前释放读锁的线程对应的读锁计数器,如果是完全释放锁,则需要销毁对应的读锁计数器。
更新同步状态的高 16 位的值,表示释放读锁。如果是完全释放锁,则当前线程去唤醒同步队列中的线程节点。注意,此时同步队列中既可能有写线程节点,也可能有读线程节点,可以想下锁降级的阻塞场景。此外,共享模式是传播性唤醒,需要好好体会下。
写锁 写锁是一个支持重入的排它锁。如果当前线程已经获取了写锁,则增加写状态。如果当前线程在获取写锁时,读锁已经被获取或者该线程不是已经获取写锁的线程,则当前线程进入等待状态。
写锁内部持有 ReentrantReadWriteLock
中的 Sync 类型的对象,可能是 FairSync
对象,也可能是 NonfairSync
对象,具体由 ReentrantReadWriteLock
构造函数决定。ReadLock 锁获取与释放功能全部委托给 sync 对象完成。
属性 1 2 3 4 5 6 7 8 9 10 11 12 13 14 +--- ReentrantReadWriteLock public static class WriteLock implements Lock , java .io .Serializable { private static final long serialVersionUID = -4992448646407690164L ; private final Sync sync; protected WriteLock (ReentrantReadWriteLock lock) { sync = lock.sync; } }
获取写锁 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 +--- WriteLock public void lock () { sync.acquire(1 ); } +--- AQS public final void acquire (int arg) { if (!tryAcquire(arg) && acquireQueued(addWaiter(Node.EXCLUSIVE), arg)) selfInterrupt(); } +--- Sync protected final boolean tryAcquire (int acquires) { Thread current = Thread.currentThread(); int c = getState(); int w = exclusiveCount(c); if (c != 0 ) { if (w == 0 || current != getExclusiveOwnerThread()) return false ; if (w + exclusiveCount(acquires) > MAX_COUNT) throw new Error("Maximum lock count exceeded" ); setState(c + acquires); return true ; } if (writerShouldBlock() || !compareAndSetState(c, c + acquires)) return false ; setExclusiveOwnerThread(current); return true ; }
写锁获取使用 AQS 的独占模式获取同步状态的流程,整个流程如下:
判断读锁是否被线程持有(包括当前线程自身),如果被持有则获取写锁直接失败。
判断是否是重入获取写锁,如果不是直接获取写锁失败。
根据具体的公平或非公平模式判断获取写锁是否需要阻塞,如果不需要阻塞则尝试获取写锁,成功后当前线程独占锁。
释放写锁 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 +--- WriteLock public void unlock () { sync.release(1 ); } +-- AQS public final boolean release (int arg) { if (tryRelease(arg)) { Node h = head; if (h != null && h.waitStatus != 0 ) unparkSuccessor(h); return true ; } return false ; } +--- Sync protected final boolean tryRelease (int releases) { if (!isHeldExclusively()) throw new IllegalMonitorStateException(); int nextc = getState() - releases; boolean free = exclusiveCount(nextc) == 0 ; if (free) setExclusiveOwnerThread(null ); setState(nextc); return free; }
写锁的释放与 ReentrantLock
的释放过程基本类似,每次释放均减少写状态,当写状态为 0 时表示写锁可以被释放。
锁降级 ReentrantReadWriteLock 锁降级指的是写锁降级成为读锁。如果当前线程拥有写锁,然后将其释放,最后再获取读锁,这种分段完成的过程不能称之为锁降级。锁降级是指持有写锁的线程在不释放写锁的同时,再获取到读锁,随后释放写锁,最后释放读锁 。
锁降级示例
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 ReentrantReadWriteLock reentrantReadWriteLock = new ReentrantReadWriteLock(); ReentrantReadWriteLock.ReadLock readLock = reentrantReadWriteLock.readLock(); ReentrantReadWriteLock.WriteLock writeLock = reentrantReadWriteLock.writeLock(); public volatile Boolean updateFlag = Boolean.FALSE; private void processData () { readLock.lock(); if (updateFlag) { readLock.unlock(); writeLock.lock(); try { if (updateFlag) { cacheData = caculateCacheData(); updateFlag = Boolean.FALSE; } readLock.lock(); } finally { writeLock.unlock(); } } try { System.out.println("print cache: " + cacheData); } finally { readLock.unlock(); } }
上述示例中,缓存数据可用时,每个线程只需获取读锁然后访问,数据访问完成后释放读锁。但当共享的缓存数据被破坏,此时所有访问 processData 方法的线程都能感知到,但只有一个线程能够获取写锁然后更新缓存,其它线程都会被阻塞。当线程更新完缓存数据后,会接着获取读锁,随后才会释放写锁,完成锁的降级。
锁降级中的读锁获取是否有必要?答案是必要的,主要是为了保证数据的可见性。如果线程计算完缓存后没有获取读锁而是直接释放掉了写锁,那么此时如果存在另一个线程 t 获取了写锁并修改了缓存,那么当前线程就无法感知线程 t 的数据更新。如果当前线程在释放掉写锁前获取读取,也就是遵循锁降级的步骤,则线程 t 就无法获取写锁,直到当前线程访问数据并释放掉读锁后,线程 t 才能有机会获取写锁更新缓存数据。
注意: ReentrantReadWriteLock 不支持锁升级,即持有读锁时再获取写锁,随后释放读锁。不支持的目的是保证数据可见性,如果读锁已被多个线程获取,其中任意线程成功获取了写锁并更新数据,则其更新对其它获取到读锁的线程是不可见的。此外,是为了避免发生死锁,试想一个线程先获取读锁,然后再获取写锁,那么该线程会由于获取写锁失败进入同步队列中等待,可能之后就不会被唤醒了。
小结 ReentrantReadWriteLock 读写锁正如其名,具体分为读锁和写锁。无论是读锁还是写锁,整个获取与释放锁的流程都是交给实现 AQS 的 Sync 类型的对象完成,准确来说是公平 Sync 或者非公平 Sync 对象。对于读锁和写锁的语义,是将同步状态 state
划分为高低位,高 16 位表示读锁状态,低 16 位表示写锁状态。写锁的获取和释放锁类似重入锁 ReentrantLock
过程,唯一不同的是写锁需要考虑读锁的占有情况。读锁的获取和释放比较复杂,复杂的主要原因是读锁允许多个线程同时获取且支持可重入,此时同步状态的高 16 位的值没办法表示各个线程持有读锁的情况,因此读写锁新增了线程读锁计数器 的概念,有了这个概念就可以很轻松判断读线程重入锁的情况以及实时获取当前线程持有读锁的次数。