【锁】ReadWriteLock之读写锁在获取锁与释放锁分析
引言
由ReentrantReadWriteLock源码可知,读锁与写锁共同使用了一个Sync同步器,即使用了同一个同步队列。
获取锁
获取读锁
1 | public void lock() { |
直接调用了同步器的共享锁。AQS中的acquireShared方法会回调Sync的tryAcquireShared(int unused)方法。
1 | public final void acquireShared(int arg) { |
tryAcquireShared方法相对来说就比较复杂了,连蒙带猜写的注释:
1 | /** |
fullTryAcquireShared方法和tryAcquireShared方法冗余度确实挺高的,只有满足以下几个条件,才会进入完整版本获取锁方法:
- 没有写锁
- 读阻塞readerShouldBlock方法返回true
- 读锁持有线程数量大于等于MAX_COUNT
- 尝试获取读锁失败
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/**
* Full version of acquire for reads, that handles CAS misses
* and reentrant reads not dealt with in tryAcquireShared.
* 获取读锁更完整版本,处理tryAcquireShared中没有解决的CAS misses和可重入读
*/
final int fullTryAcquireShared(Thread current) {
/*
* This code is in part redundant with that in
* tryAcquireShared but is simpler overall by not
* complicating tryAcquireShared with interactions between
* retries and lazily reading hold counts.
* 这段代码与tryAcquireShared中的代码在一定程度上是冗余的,但总的来说更简单,
* 因为它没有使tryAcquireShared在重试和延迟读取hold count之间的交互复杂化。
*/
HoldCounter rh = null;
//循环
for (; ; ) {
//获取当前锁状态
int c = getState();
//c中表示独占持有的数量不为0,并且当前线程并不是获取写锁的线程。
if (exclusiveCount(c) != 0) {
if (getExclusiveOwnerThread() != current)
return -1;
// else we hold the exclusive lock; blocking here
// would cause deadlock.
//查看是否阻塞读锁
} else if (readerShouldBlock()) {
// Make sure we're not acquiring read lock reentrantly
// 确保我们没有重入读锁
if (firstReader == current) {
// assert firstReaderHoldCount > 0;
} else {
if (rh == null) {
rh = cachedHoldCounter;
//获取当前线线程所持有的读锁数量
if (rh == null || rh.tid != getThreadId(current)) {
rh = readHolds.get();
if (rh.count == 0)
//ThreadLocal移除当前线程
readHolds.remove();
}
}
//读锁数量为0
if (rh.count == 0)
return -1;
}
}
//如果读锁数量等罪最大值,抛出Error
if (sharedCount(c) == MAX_COUNT)
throw new Error("Maximum lock count exceeded");
//CAS尝试获取读锁
if (compareAndSetState(c, c + SHARED_UNIT)) {
//如果读锁数量为0
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; // cache for release
}
return 1;
}
}
}
获取写锁
直接看代码注释即可。
1 | /** |
释放锁
释放读锁
1 | /** |
释放写锁
1 | /* |
从获取锁与释放锁的源码中可以看出几个比较重要的属性:
- firstReader和firstReaderHoldCount
使用这两个属性的目的应该是优化性能,因为读锁重入或者读锁释放的时候可以直接操作,而不必去用ThreadLocal。 - cachedHoldCounter和readHolds
保存了读锁持有的非第一读者的其他线程与其持有的重入数量。 - exclusiveOwnerThread
存储独占模式同步的当前所有者。
本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来自 ClawHub的技术分享!