ReadWriteLock
public interface ReadWriteLock {
/**
* Returns the lock used for reading.
*
* @return the lock used for reading
*/
Lock readLock();
/**
* Returns the lock used for writing.
*
* @return the lock used for writing
*/
Lock writeLock();
}
ReentrantReadWriteLock类是接口ReadWriteLock的具体实现类,支持类似于ReentrantLock的语义。
ReentrantReadWriteLock的介绍翻译:
此类不会对锁访问强加读取器或写入器首选项排序。但是,它确实支持可选的公平策略。
非公平模式:当构造为非公平(默认)时,读写锁的进入顺序是未指定的,受可重入约束。连续争用的非公平锁可以无限期地推迟一个或多个读写器线程,但通常比公平锁具有更高的吞吐量。
公平模式:当构造为公平时,线程使用近似到达顺序策略来竞争进入。当当前持有的锁被释放时,等待时间最长的单个写入线程将被分配写入锁,或者如果有一组读取线程等待的时间超过所有等待的写入线程,则该组将被分配读取锁。
如果写入锁被保持,或者存在等待的写入线程,则试图获取公平读取锁(不可重入)的线程将被阻塞。线程将不会获取读锁,直到最老的当前等待写入线程获取并释放写锁之后。当然,如果等待的写入器放弃等待,让一个或多个读线程成为队列中最长的等待者,并释放写锁,那么这些读线程将被分配读锁。
试图获取公平写锁(不可重入)的线程将阻塞,除非读锁和写锁都是空闲的(这意味着没有等待线程)。(请注意,非阻塞的{ReadLock#tryLock()}和{WriteLock#tryLock()}方法不支持此公平设置,如果可能,将立即获取锁,而不考虑等待的线程。)
可重入:
此锁允许读取器和写入器以{@link ReentrantLock}的样式重新获取读或写锁。在写入线程持有的所有写入锁都被释放之前,不允许使用不可重入读取器。
此外,写入程序可以获取读锁,但反之亦然。在其他应用程序中,当在调用或回调在读锁下执行读取的方法时保持写锁时,可重入性非常有用。如果读取器试图获取写锁,它将永远不会成功。
锁降级:
重新进入还允许通过获取写锁,然后获取读锁,然后释放写锁,从写锁降级为读锁。但是,从读锁升级到写锁是不可能的。
锁获取中断:
读锁和写锁都支持在锁获取期间中断。
Condition的支持
写锁提供了一个{Condition}实现,该实现在写锁方面的行为与{ReentrantLock#newCondition}提供的{Condition}实现对{RentrantLock}的行为相同。当然,这个{@link Condition}只能与写锁一起使用。
读取锁不支持{Condition},{readLock().newCondition()}抛出{UnsupportedOperationException}。
ReentrantReadWriteLocks可用于提高某些类型集合的某些使用的并发性。这通常只在集合预期很大、由更多的读线程访问而不是写线程访问,并且需要开销大于同步开销的操作时才有价值。例如,这里有一个使用TreeMap的类,该类应该很大,并且可以并发访问。
ReentrantReadWriteLock的内部类关系
ReentrantReadWriteLock的简单使用方式
public class JucTest {
static ReentrantReadWriteLock readWriteLock = new ReentrantReadWriteLock();
static ReentrantReadWriteLock.ReadLock readLock = readWriteLock.readLock();
static ReentrantReadWriteLock.WriteLock writeLock = readWriteLock.writeLock();
public static void main(String[] args) {
try{
readLock.lock();
}finally {
readLock.unlock();
}
readWriteLock.getReadLockCount();
try{
writeLock.lock();
}finally {
writeLock.unlock();
}
}
}
ReentrantReadWriteLock源码流程分析
构造方法
ReentrantReadWriteLock默认也是非公平锁,通过构造方法的参数true或false来选择。true是公平锁,false是非公平锁。
同时,对于读锁和写锁,也会随着参数设置自身为公平锁还是非公平锁。
private final ReentrantReadWriteLock.ReadLock readerLock;
private final ReentrantReadWriteLock.WriteLock writerLock;
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);
}
public ReentrantReadWriteLock.WriteLock writeLock() { return writerLock; }
public ReentrantReadWriteLock.ReadLock readLock() { return readerLock; }
内部类Sync说明
- 因为Sync类是FairSync和NonfairSync的父类,所以在new 这俩子类时,会先初始化Sync类,执行new Sync()构造方法。
- Sync类里有两个内部类:HoldCounter、ThreadLocalHoldCounter,在Sync类初始化的时候也完成了初始化,这两类的作用是多个不同线程进行共享锁获取,每个线程在自己拥有的副本里,记录自己的重入数。
- 提供了sharedCount、exclusiveCount方法返回当前的共享持有数和独占持有数。
- 提供了readerShouldBlock、writerShouldBlock两个抽象方法供它的子类,FairSync和NonfairSync去实现。
- 因为继承AQS类,就必须要要实现指定的方法,这些都在Sync类里被实现了。
ReentrantReadWriteLock.Sync#tryAcquire方法执行流程解释
- 如果读计数非零或写计数非零且所有者是不同的线程,则失败。
- 如果计数饱和,则失败,抛出异常。(这只会在count已经非零的情况下发生。)
- 如果writerShouldBlock返回true(在公平锁时会有可能返回true)获取锁失败
- 尝试获取锁,更新state值失败,获取锁失败。
- 否则,如果是,更新状态并设置所有者。
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;
}
if (writerShouldBlock() ||
!compareAndSetState(c, c + acquires))
return false;
setExclusiveOwnerThread(current);
return true;
}
ReentrantReadWriteLock.Sync#tryRelease方法执行流程解释
- 如果不是当前线程持有锁,那么就不该执行该方法,抛出异常。
- state值进行相应的减去,并将结果更新。
- 如果此时独占式锁都释放了,那么就移除持锁的线程。
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;
}
ReentrantReadWriteLock.Sync#tryAcquireShared方法执行流程解释
- 如果写锁(独占式)被另一个线程持有,则失败。否则,该线程符合锁定wrt状态的条件。
- 共享模式下允许多线程读,即readerShouldBlock等于false。公平锁的时候会根据当前线程在队列的位置返回true或false,非公平锁时要判断队列的线程是否存在以及线程是否共享等,返回true或false。
- 允许读共享,且尝试获取锁成功。那么就根据当前共享锁的个数情况,选择更新持有个数、第一个获取锁的线程,以及更新在守护线程里的个数和线程信息。
- 不允许读共享或者此时获取锁失败了,那么以死循环方式执行尝试获取共享模式锁的过程,成功后更新共享信息,失败后返回-1
protected final int tryAcquireShared(int unused) {
/*
* Walkthrough:
* 1. If write lock held by another thread, fail.
* 2. Otherwise, this thread is eligible for
* lock wrt state, so ask if it should block
* because of queue policy. If not, try
* to grant by CASing state and updating count.
* Note that step does not check for reentrant
* acquires, which is postponed to full version
* to avoid having to check hold count in
* the more typical non-reentrant case.
* 3. If step 2 fails either because thread
* apparently not eligible or CAS fails or count
* saturated, chain to version with full retry loop.
*/
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 {
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);
}
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.
*/
HoldCounter rh = null;
for (;;) {
int c = getState();
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)
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; // cache for release
}
return 1;
}
}
}
ReentrantReadWriteLock.Sync#tryReleaseShared方法执行流程解释
- 如果当前线程就是共享锁的第一个线程,那么就对应的进行统计个数减一,或者重置第一个线程
- 如果不是第一个线程,那么就从ThreadLocal缓存里获取加锁的线程,并进行个数减一
- 死循环方式使用CAS进行state值的修改,如果修改成功且共享锁完全释放,则return true,否则false。
protected final boolean tryReleaseShared(int unused) {
Thread current = Thread.currentThread();
if (firstReader == current) {
// assert firstReaderHoldCount > 0;
if (firstReaderHoldCount == 1)
firstReader = null;
else
firstReaderHoldCount--;
} else {
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))
// Releasing the read lock has no effect on readers,
// but it may allow waiting writers to proceed if
// both read and write locks are now free.
return nextc == 0;
}
}
在线程持有读锁的情况下,该线程不能取得写锁(因为获取写锁的时候,如果发现当前的读锁被占用,就马上获取失败,不管读锁是不是被当前线程持有)。
在线程持有写锁的情况下,该线程可以继续获取读锁(获取读锁时如果发现写锁被占用,只有写锁没有被当前线程占用的情况才会获取失败)。
一个线程要想同时持有写锁和读锁,必须先获取写锁再获取读锁;写锁可以“降级”为读锁;读锁不能“升级”为写锁。
读锁加锁
因为ReadLock实现了Lock接口,并且组合使用了内部类Sync,在读锁进行加锁执行lock()方法时,实际上还是访问了AQS类的acquireShared(1)方法。
在该方法里先执行内部类Sync实现的tryAcquireShared(1)方法,尝试获取共享模式的锁,如果失败(返回结果小于0),就执行doAcquireShared(1)方法。
读锁释放锁
读锁释放锁实际还是访问了AQS类的releaseShared方法。AQS类根据多态执行的是Sync类里的tryReleaseShared方法。如果共享锁都完全释放了,才执行队列唤醒功能。
写锁加锁
访问的是AQS的acquire方法,然后再执行ReentrantReadWriteLock.Sync类的tryAcquire方法。后续流程与ReentrantLock加锁的执行流程和方式一致
写锁释放锁
访问的是AQS的release方法,然后再执行ReentrantReadWriteLock.Sync类的tryRelease方法。后续流程与ReentrantLock加锁的执行流程和方式一致。