ReentrantReadWriteLock

Catalogue
  1. 1. 写锁
  2. 2. 读锁
  3. 3. 锁降级

ReentrantReadWriteLock是读写锁,其维护了一对锁,一个读锁和一个写锁,读写锁在同一时刻可以允许多个读线程访问,在写线程访问时,则所有线程均被阻塞。适用于读多写少的场景。

如何在一个int变量上维护多个读线程和一个写线程的状态。读写锁将int变量分成了两个部分,高16位表示读,低16位表示写。通过位运算来实现状态的改变。

写锁

写锁是独占锁,获取锁时重写tryAcquire即可

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
protected final boolean tryAcquire(int acquires) {
/*
* Walkthrough:
* 1. If read count nonzero or write count nonzero
* and owner is a different thread, fail.
* 2. If count would saturate, fail. (This can only
* happen if count is already nonzero.)
* 3. Otherwise, this thread is eligible for lock if
* it is either a reentrant acquire or
* queue policy allows it. If so, update state
* and set owner.
*/
Thread current = Thread.currentThread();
int c = getState();
//获取代表写锁的值,采用位运算来实现
int w = exclusiveCount(c);
if (c != 0) {
// (Note: if c != 0 and w == 0 then shared count != 0)
// 存在读锁或者当前线程不是已经获取写锁的线程
if (w == 0 || current != getExclusiveOwnerThread())
return false;
if (w + exclusiveCount(acquires) > MAX_COUNT)
throw new Error("Maximum lock count exceeded");
// Reentrant acquire
setState(c + acquires);
return true;
}
// 在c=0时即没有读锁和写锁时,非公平锁会交给AQS去创建头节点然后获取再次调用获取锁
if (writerShouldBlock() ||
!compareAndSetState(c, c + acquires))
return false;
setExclusiveOwnerThread(current);
return true;
}

在读锁存在的时候,写锁不能被获取,原因在于:读写锁要保证写锁的操作要对读锁可见,如果存在读锁时写锁获取到,写线程的操作就对读线程不可见。而写锁一旦被获取,则其他读写线程的后续访问就会被阻塞。

方法writerShouldBlock由Sync的子类来实现,以此来区分公平锁和非公平锁。

写锁的释放操作和ReentrantLock类似,需要保证可重入性。

读锁

读锁是共享锁,利用tryAcquireSharedtryReleaseShared来进行锁的获取和释放。如果其他线程已经获取了写锁,则当前线程获取读锁失败,进入等待状态,如果当前线程获取了写锁或者写锁未被获取,则当前线程增加读状态,获取读锁。

读锁的每次释放都会减少读状态。注意在增加读状态和减少读状态时都需要保证线程安全(CAS),因为可能有多个线程对状态进行修改。

锁降级

锁降级指的是写锁降级成为读锁:获取到写锁,再获取到读锁,随后释放写锁的过程。