ReadWriteLock
类图
ReadWriteLock
Sync
ReentrantReadWriteLock内部的Sync的类图如下:
构造
默认构造器:
1public ReentrantReadWriteLock() {
2 this(false);
3}
含参构造器:
1public ReentrantReadWriteLock(boolean fair) {
2 sync = fair ? new FairSync() : new NonfairSync();
3 readerLock = new ReadLock(this);
4 writerLock = new WriteLock(this);
5}
写锁和读锁的构造器是一个套路,以读锁为例:
1protected ReadLock(ReentrantReadWriteLock lock) {
2 sync = lock.sync;
3}
可以看出,读写锁内部的sync其实就是ReentrantReadWriteLock的sync。
读锁
lock
ReadLock.lock:
1public void lock() {
2 sync.acquireShared(1);
3}
AbstractQueuedSynchronizer.acquireShared:
1public final void acquireShared(int arg) {
2 if (tryAcquireShared(arg) < 0)
3 doAcquireShared(arg);
4}
tryAcquireShared方法的实现位于Sync:
1protected final int tryAcquireShared(int unused) {
2 Thread current = Thread.currentThread();
3 int c = getState();
4 if (exclusiveCount(c) != 0 &&
5 getExclusiveOwnerThread() != current)
6 return -1;
7 int r = sharedCount(c);
8 if (!readerShouldBlock() &&
9 r < MAX_COUNT &&
10 compareAndSetState(c, c + SHARED_UNIT)) {
11 if (r == 0) {
12 firstReader = current;
13 firstReaderHoldCount = 1;
14 } else if (firstReader == current) {
15 firstReaderHoldCount++;
16 } else {
17 HoldCounter rh = cachedHoldCounter;
18 if (rh == null || rh.tid != getThreadId(current))
19 cachedHoldCounter = rh = readHolds.get();
20 else if (rh.count == 0)
21 readHolds.set(rh);
22 rh.count++;
23 }
24 return 1;
25 }
26 return fullTryAcquireShared(current);
27}
以下进行分部分说明。
排它锁/写锁检测
如果另一个线程已经持有写锁/排它锁,那么读锁的获得将会马上失败。此部分源码:
1Thread current = Thread.currentThread();
2int c = getState();
3if (exclusiveCount(c) != 0 && getExclusiveOwnerThread() != current)
4 return -1;
getState值在此处被当做两个short来使用,高16位值代表读锁的持有次数,低16位代表写锁的的持有次数。
1static final int SHARED_SHIFT = 16;
2static final int SHARED_UNIT = (1 << SHARED_SHIFT);
3static final int MAX_COUNT = (1 << SHARED_SHIFT) - 1;
4static final int EXCLUSIVE_MASK = (1 << SHARED_SHIFT) - 1;
5
6static int sharedCount(int c) { return c >>> SHARED_SHIFT; }
7
8static int exclusiveCount(int c) { return c & EXCLUSIVE_MASK; }
这就说明,读锁会被写锁阻塞。
应该阻塞?
对应于readerShouldBlock方法,对于公平锁和非公平锁有两种不同的语义。
非公平锁
NonfairSync.readerShouldBlock:
1final boolean readerShouldBlock() {
2 return apparentlyFirstQueuedIsExclusive();
3}
AbstractQueuedSynchronizer.apparentlyFirstQueuedIsExclusive:
1final boolean apparentlyFirstQueuedIsExclusive() {
2 Node h, s;
3 return (h = head) != null && (s = h.next) != null &&
4 !s.isShared() && s.thread != null;
5}
可以看出,此方法在非公平锁的情况下主要是检测当前锁队列中第一个元素是不是写锁(排它锁),如果是,那么当前线程主动放弃竞争锁的机会,这样做是为了防止出现写锁饥饿的现象。
公平锁
FairSync.readerShouldBlock:
1final boolean readerShouldBlock() {
2 return hasQueuedPredecessors();
3}
AbstractQueuedSynchronizer.hasQueuedPredecessors:
1public final boolean hasQueuedPredecessors() {
2 Node t = tail; // Read fields in reverse initialization order
3 Node h = head;
4 Node s;
5 return h != t &&
6 ((s = h.next) == null || s.thread != Thread.currentThread());
7}
这个就很好理解了,锁队列中前面如果还有其它等待锁的线程,那么就应该阻塞。
快速尝试
如果满足所有的条件(没有写锁、不应该阻塞,没有达到读锁可重入次数的上限),那么便会进行一次快速尝试,如果失败,再进行入队(锁队列)的复杂操作。
快速尝试其实就是一个CAS操作,源码见上面,再次不再 赘述。
完全尝试
所谓的完全尝试便是在死循环里执行快速尝试,直到成功为止。
入队等待
上面提到的快速尝试和完全尝试都是在当前没有其它线程持有写锁的情况下,如果写锁被其它线程持有,那么只能将当前线程加入到锁队列排队。
unlock
ReadLock.unlock:
1public void unlock() {
2 sync.releaseShared(1);
3}
AbstractQueuedSynchronizer.releaseShared:
1public final boolean releaseShared(int arg) {
2 if (tryReleaseShared(arg)) {
3 doReleaseShared();
4 return true;
5 }
6 return false;
7}
Sync.tryReleaseShared:
1protected final boolean tryReleaseShared(int unused) {
2 Thread current = Thread.currentThread();
3 if (firstReader == current) {
4 // assert firstReaderHoldCount > 0;
5 if (firstReaderHoldCount == 1)
6 firstReader = null;
7 else
8 firstReaderHoldCount--;
9 } else {
10 HoldCounter rh = cachedHoldCounter;
11 if (rh == null || rh.tid != getThreadId(current))
12 rh = readHolds.get();
13 int count = rh.count;
14 if (count <= 1) {
15 readHolds.remove();
16 if (count <= 0)
17 throw unmatchedUnlockException();
18 }
19 --rh.count;
20 }
21 for (;;) {
22 int c = getState();
23 int nextc = c - SHARED_UNIT;
24 if (compareAndSetState(c, nextc))
25 return nextc == 0;
26 }
27}
ReadWriteLock采用了ThreadLocal来记录线程重入读锁的次数,这么做的原因是允许多个线程同时拥有读锁。
写锁
lock
源码:
1public void lock() {
2 sync.acquire(1);
3}
AbstractQueuedSynchronizer.acquire:
1public final void acquire(int arg) {
2 if (!tryAcquire(arg) && acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
3 selfInterrupt();
4}
此方法在说明ReentrantLock的时候已经见过了。
Sync.tryAcquire:
1protected final boolean tryAcquire(int acquires) {
2 Thread current = Thread.currentThread();
3 int c = getState();
4 int w = exclusiveCount(c);
5 if (c != 0) {
6 // (Note: if c != 0 and w == 0 then shared count != 0)
7 if (w == 0 || current != getExclusiveOwnerThread())
8 return false;
9 if (w + exclusiveCount(acquires) > MAX_COUNT)
10 throw new Error("Maximum lock count exceeded");
11 // Reentrant acquire
12 setState(c + acquires);
13 return true;
14 }
15 if (writerShouldBlock() ||
16 !compareAndSetState(c, c + acquires))
17 return false;
18 setExclusiveOwnerThread(current);
19 return true;
20}
很明显,申请写锁的套路是这样的: 如果有其它线程持有读锁或写锁,那么失败,否则尝试进行写锁获取。
writerShouldBlock方法对应读锁里的readerShouldBlock方法,后者可以参见前面读锁-应该阻塞?一节。而对于writerShouldBlock来说同样分为两种情况,即公平锁与非公平锁。公平锁的实现目的与readerShouldBlock相同,即判断等待队列中是否有先到的等待者。
而readerShouldBlock的非公平锁实现的目的在于防止写锁出现饥饿的情况,对于writerShouldBlock来说就不需要作此考量,所以NonfairSync.writerShouldBlock直接返回false。
unlock
写锁的释放无非是一个减少重入次数、更改锁拥有线程以及通知后继的过程,不再赘述。