ReentrantReadWriteLock
内部结构
1 | public ReentrantReadWriteLock(boolean fair) { |
继承关系:
1 | public class ReentrantReadWriteLock |
java.util.concurrent.locks.ReentrantReadWriteLock.Sync
这个同步器的实现中 state 字段的定义:
read count: state 的前两个字节(高字节)
write count: state 的后两个字节(低字节)
Lock state is logically divided into two unsigned shorts:
The lower one representing the exclusive (writer) lock hold count,
The upper the shared (reader) hold count.
ReadLock
- lock 过程
java.util.concurrent.locks.ReentrantReadWriteLock.ReadLock.lock
1 | public void lock() { |
java.util.concurrent.locks.AbstractQueuedSynchronizer.acquireShared
1 | public final void acquireShared(int arg) { |
其中,tryAcquireShared 方法被 Sync类 override
java.util.concurrent.locks.ReentrantReadWriteLock.Sync.tryAcquireShared
1 | protected final int tryAcquireShared(int unused) { |
由上面的代码可知:获取读锁的条件就是
!readerShouldBlock() &&
r < MAX_COUNT &&
compareAndSetState(c, c + SHARED_UNIT)
其实就是 readerShouldBlock 返回 false,读锁就可以被获取到。
ReentrantReadWriteLock.Sync.fullTryAcquireShared
1 | final int fullTryAcquireShared(Thread current) { |
readerShouldBlock,在什么情况下会出现,没有写线程的情况下,还需要读线程block呢?估计和 write lock 的 condition 有关。
fullTryAcquireShared 方法在成功获得锁时,设置状态们时并没有对重入的情况区别对待,而是直接,compareAndSetState(c, c + SHARED_UNIT),这也说明一个问题,对于通过重入获得的读锁的线程,这个线程也将被认为另是一个获得读锁的线程: read lock count + 1
那么,能不能区分呢,可以:
1 | HoldCounter rh = readHolds.get(); |
之所以不区分,原因估计是出于性能考虑。
所以读锁有 65535 并不表示可以有 65535 线程可以获得锁,如果有一个线程重入一次,则这次重入也将导致 读锁 减少,此时只有 65535 - 1 个线程可以获得锁,而不是 65535 个。
- release 过程
读锁的释放
ReentrantReadWriteLock.Sync.tryReleaseShared
这个方法会被多个 读线程 并发调用
1 | protected final boolean tryReleaseShared(int unused) { |
这个释放 read lock 的,貌似,read lock 如果重入了,只能调用一次 release,否则会出问题。
这里也是直接释放锁,而没有区分是否是重入的线程,因为获得锁时也没有区分,所以这么做是合理的
对 Condition 的支持
1 | public Condition newCondition() { |
读锁不支持Condition
WriteLock
- lock 过程
ReentrantReadWriteLock.Sync.tryAcquire
1 | protected final boolean tryAcquire(int acquires) { |
- unlock 过程
ReentrantReadWriteLock.Sync.tryRelease
这个方法是写线程独占调用的。所以可以直接使用 setState
来设置 state 字段。
1 | protected final boolean tryRelease(int releases) { |
对 Condition 的支持
ReentrantReadWriteLock.WriteLock.newCondition()
1
2
3public Condition newCondition() {
return sync.newCondition();
}ReentrantReadWriteLock.Sync.newCondition()
1
2
3final ConditionObject newCondition() {
return new ConditionObject();
}
写锁支持Condition
WriteLock & ReadLock
state
这个字段的,高字表示 read lock count,低字表示 write lock count。
但其含义却不相同,read lock count 表示有多少个线程,拥有了锁,而write lock count表示当前拥有锁的线程的请求锁的重入次数。
所以,write lock 可以支持单个线程 65535 次重入,而 read lock 则支持 65535 个线程获得锁。当超过这个数值时,lock 方法就会抛异常。
1 | throw new Error("Maximum lock count exceeded"); |
This lock supports a maximum of 65535 recursive write locks and 65535 read locks. Attempts to exceed these limits result in Error throws from locking methods.
重入的支持
读线程和写线程都支持重入。此外,写线程可以请求读锁,但是反过来却不行,所以如果一个 reader 试图到请求读锁将不会成功。
read 线程 如何维护其重入状态
state 字段中的 read lock count 表示的是获得 read lock的线程数量。那对于一个 read 线程其重入的数值如何记录呢?同时对于独占的写线程来说,记录其重入的次数,其意义在于当重入次数等于0的时候,释放其对于锁的独占,也就是:
ReentrantReadWriteLock.Sync.tryRelease:
1 | boolean free = exclusiveCount(nextc) == 0; |
但是对于,read锁来说,因为其并不会独占线程,也就是说不会设置 setExclusiveOwnerThread 成当前线程,因为多个 read 线程是并发执行的,也就没有所谓当前线程了。那么对于 read 线程是否还需要维护其重入的次数呢? ReadLock 对重入状态的维护在 tryAcquireShared 和 tryReleaseShared 中。
其时维护重入次数是有意义的,保证 read lock 的 relase 方法能够被正确地调用:
1 | int count = rh.count; |
写锁中使用读锁
可以使用,不会造成死锁。符合读写及并发的逻辑,写的时候去读,不会造成并发问题,而读的时候去写,其它正在读的线程则有可能读到脏数据,从而引发同步问题。
读锁中使用写锁
不可以使用,如果这样使用了,则当前线程将被放到 sync queue 中。从而进入 park(block) 状态,死锁就发生了。当前线程持有读锁,但是又被 park 了。而只有所有读写锁free的时候,队列中的wait的 写线程才可以被唤醒,循环了,死锁发生了。