java源码分析—ReadWriteLock(下)
1. java源码中的ReadWriteLock的实现
Java并发库中ReetrantReadWriteLock实现了ReadWriteLock接口并添加了可重入的特性。
2. 特性
ReentrantReadWriteLock有如下特性:
(1)获取顺序
- 非公平模式(默认)
当以非公平初始化时,读锁和写锁的获取的顺序是不确定的。非公平锁主张竞争获取,可能会延缓一个或多个读或写线程,但是会比公平锁有更高的吞吐量。
- 公平模式
当以公平模式初始化时,线程将会以队列的顺序获取锁。当当前线程释放锁后,等待时间最长的写锁线程就会被分配写锁;或者有一组读线程组等待时间比写线程长,那么这组读线程组将会被分配读锁。
当有写线程持有写锁或者有等待的写线程时,一个尝试获取公平的读锁(非重入)的线程就会阻塞。这个线程直到等待时间最长的写锁获得锁后并释放掉锁后才能获取到读锁。
(2)可重入
允许读锁和写锁可重入。写锁可以获得读锁,读锁不能获得写锁。
(3)锁降级
允许写锁降低为读锁
(4)中断锁的获取
在读锁和写锁的获取过程中支持中断
(5)支持Condition
写锁提供Condition实现
(6)监控
提供确定锁是否被持有等辅助方法
3. 属性
/** Inner class providing readlock */
private final ReentrantReadWriteLock.ReadLock readerLock;
/** Inner class providing writelock */
private final ReentrantReadWriteLock.WriteLock writerLock;
/** Performs all synchronization mechanics */
final Sync sync;
ReentrantReadWriteLock有三个属性,分别对应其三个内部类,接下来我们会详细分析这三个内部类
4. 构造方法
/**
* Creates a new {@code ReentrantReadWriteLock} with
* default (nonfair) ordering properties.
*/
public ReentrantReadWriteLock() {
this(false);
}
/**
* Creates a new {@code ReentrantReadWriteLock} with
* the given fairness policy.
*
* @param fair {@code true} if this lock should use a fair ordering policy
*/
public ReentrantReadWriteLock(boolean fair) {
sync = fair ? new FairSync() : new NonfairSync();
readerLock = new ReadLock(this);
writerLock = new WriteLock(this);
}
ReentrantReadWriteLock有两个构造方法,默认的构造方法使用的是非公平模式,创建的Sync是NonfairSync对象,然后初始化读锁和写锁。一旦初始化后,ReadWriteLock接口中的两个方法就有返回值了,如下:
public ReentrantReadWriteLock.WriteLock writeLock() { return writerLock; }
public ReentrantReadWriteLock.ReadLock readLock() { return readerLock; }
从上面可以看到,构造方法决定了Sync是FairSync还是NonfairSync。Sync继承了AbstractQueuedSynchronizer,而Sync是一个抽象类,NonfairSync和FairSync继承了Sync,并重写了其中的抽象方法。
5. 内部类
5.1 内部类Sync
abstract static class Sync extends AbstractQueuedSynchronizer
Sync是继承自AbstractQueuedSynchronizer实现的抽象类,NonFairSync/FairSync都是Sync的子类,分别代表非公平同步器和公平同步器。
在Sync里面有两个虚拟方法是需要其子类实现的,源码如下:
/**
* Returns true if the current thread, when trying to acquire
* the read lock, and otherwise eligible to do so, should block
* because of policy for overtaking other waiting threads.
*/
abstract boolean readerShouldBlock();
/**
* Returns true if the current thread, when trying to acquire
* the write lock, and otherwise eligible to do so, should block
* because of policy for overtaking other waiting threads.
*/
abstract boolean writerShouldBlock();
writerShouldBlock和readerShouldBlock方法都表示当有别的线程也在尝试获取锁时,是否应该阻塞。
接着看下Sync的实现类:FairSync和NonfairSync
5.1.1 FairSync
static final class FairSync extends Sync {
private static final long serialVersionUID = -2274990926593161451L;
final boolean writerShouldBlock() {
return hasQueuedPredecessors();
}
final boolean readerShouldBlock() {
return hasQueuedPredecessors();
}
通过源码看到FairSync在实现两个虚拟方法是用到了hasQueuedPredecessors这个方法,继续跟踪源码:
public final boolean hasQueuedPredecessors() {
// The correctness of this depends on head being initialized
// before tail and on head.next being accurate if the current
// thread is first in queue.
Node t = tail; // Read fields in reverse initialization order
Node h = head;
Node s;
return h != t &&
((s = h.next) == null || s.thread != Thread.currentThread());
}
对于公平模式,hasQueuedPredecessors()方法表示前面是否有等待线程。一旦前面有等待线程,那么为了遵循公平,当前线程也就应该被挂起
5.1.2 NonfairSync
static final class NonfairSync extends Sync {
private static final long serialVersionUID = -8159625535654395037L;
final boolean writerShouldBlock() {
return false; // writers can always barge
}
final boolean readerShouldBlock() {
/* As a heuristic to avoid indefinite writer starvation,
* block if the thread that momentarily appears to be head
* of queue, if one exists, is a waiting writer. This is
* only a probabilistic effect since a new reader will not
* block if there is a waiting writer behind other enabled
* readers that have not yet drained from the queue.
*/
return apparentlyFirstQueuedIsExclusive();
}
}
从上面可以看到,非公平模式下,writerShouldBlock直接返回false,说明不需要阻塞;而readShouldBlock调用了apparentFirstQueuedIsExcluisve()方法。该方法在当前线程是写锁占用的线程时,返回true;否则返回false。也就说明,如果当前有一个写线程正在写,那么该读线程应该阻塞。 看下apparentFirstQueuedIsExcluisve方法源码:
/**
* Returns {@code true} if the apparent first queued thread, if one
* exists, is waiting in exclusive mode. If this method returns
* {@code true}, and the current thread is attempting to acquire in
* shared mode (that is, this method is invoked from {@link
* #tryAcquireShared}) then it is guaranteed that the current thread
* is not the first queued thread. Used only as a heuristic in
* ReentrantReadWriteLock.
*/
final boolean apparentlyFirstQueuedIsExclusive() {
Node h, s;
return (h = head) != null &&
(s = h.next) != null &&
!s.isShared() &&
s.thread != null;
}
注意:
继承AQS的类都需要使用state变量代表某种资源,ReentrantReadWriteLock中的state代表了读锁的数量和写锁的持有与否,整个结构如下:
可以看到state的高16位代表读锁的个数;低16位代表写锁的状态。
5.2 内部类WriteLock
写锁类,当我们写数据的时候需要加写锁。
写锁和读锁有一些规则,也是其内部实现的基础。
例如:
1、读锁可以同时被多线程所持有,而写锁只能被一个线程持有且持有的同时不允许其他线程持有读、写锁。
2、可重入性
3、可降级性
4、两个锁所持有的个数由AQS状态位state的高低16来记录。
以上的这些特性都是此类实现的内部基础。
public static class WriteLock implements Lock, java.io.Serializable
WriteLock实现了Lock和Serializable接口,我们具体看下WriteLock的的lock方法,源码:
public void lock() {
sync.acquire(1);
}
AQS中的acqure方法源码如下:
public final void acquire(int arg) {
if (!tryAcquire(arg) &&
acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
selfInterrupt();
}
从上面可以看到,写锁使用的是AQS的独占模式。首先尝试获取锁,如果获取失败,那么将会把该线程加入到等待队列中。
Sync实现了tryAcquire方法用于尝试获取一把锁,如下:
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;
}
从代码和注释可以看到,获取写锁时有三步:
1. 如果当前有写锁或者读锁。如果只有读锁,返回false,因为这时如果可以写,那么读线程得到的数据就有可能错误;如果有写锁,但是线程不同,即不符合写锁重入规则,返回false
2. 如果写锁的数量将会超过最大值65535,抛出异常;否则,写锁重入
3. 如果没有读锁或写锁的话,如果需要阻塞或者CAS失败,返回false;否则将当前线程置为获得写锁的线程
从上面可以看到调用了writerShouldBlock方法,FairSync的实现是如果等待队列中有等待线程,则返回false,说明公平模式下,只要队列中有线程在等待,那么后来的这个线程也是需要记入队列等待的;NonfairSync中的直接返回的直接是false,说明不需要阻塞。从上面的代码可以得出,当没有锁时,如果使用的非公平模式下的写锁的话,那么返回false,直接通过CAS就可以获得写锁。
WriteLock中锁的释放方法unlock(),源码如下:
public void unlock() {
sync.release(1);
}
Sync的release方法使用的AQS中的,如下:
public final boolean release(int arg) {
if (tryRelease(arg)) {
Node h = head;
if (h != null && h.waitStatus != 0)
unparkSuccessor(h);
return true;
}
return false;
}
调用tryRelease尝试释放锁,一旦释放成功了,那么如果等待队列中有线程再等待,那么调用unparkSuccessor将下一个线程解除挂起。
Sync需要实现tryRelease方法,如下
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;
}
从上面可以看到,写锁的释放主要有三步:
1. 如果当前没有线程持有写锁,但是还要释放写锁,抛出异常
2. 得到解除一把写锁后的状态,如果没有写锁了,那么将AQS的线程置为null
3. 不管第二步中是否需要将AQS的线程置为null,AQS的状态总是要更新的
从上面可以看到,返回true当且只当没有写锁的情况下,还有写锁则返回false。
5.3 内部类ReadLock
读锁类,当我们读取数据的时候需要加读锁。
public static class ReadLock implements Lock, java.io.Serializable
ReadLock实现了Lock和Serializable接口,接下来我们具体看下ReadLock的lock方法,源码:
public void lock() {
sync.acquireShared(1);
}
从代码可以看到,读锁使用的是AQS的共享模式,AQS的acquireShared方法如下:
public final void acquireShared(int arg) {
if (tryAcquireShared(arg) < 0)
doAcquireShared(arg);
}
当tryAcquireShared()方法小于0时,那么会执行doAcquireShared方法将该线程加入到等待队列中。
Sync实现了tryAcquireShared方法,如下:
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);
}
从上面的代码以及注释可以看到,分为三步:
1. 如果当前有写线程并且本线程不是写线程,那么失败,返回-1
2. 否则,说明当前没有写线程或者本线程就是写线程(可重入),接下来判断是否应该读线程阻塞并且读锁的个数是否小于最小值,并且CAS成功使读锁+1,成功,返回1。其余的操作主要是用于计数的
3. 如果2中失败了,失败的原因有三,第一是应该读线程应该阻塞;第二是因为读锁达到了上线;第三是因为CAS失败,有其他线程在并发更新state,那么会调动fullTryAcquireShared方法。
fullTryAcquireShared方法源码如:
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;
}
}
}
从上面可以看到fullTryAcquireShared与tryAcquireShared有很多类似的地方。
在上面可以看到多次调用了readerShouldBlock方法,对于公平锁,只要队列中有线程在等待,那么将会返回true,也就意味着读线程需要阻塞;对于非公平锁,如果当前有线程获取了写锁,则返回true。一旦不阻塞,那么读线程将会有机会获得读锁。
ReadLock的锁释放方法unlock(),源码如下:
public void unlock() {
sync.releaseShared(1);
}
调用了Sync的releaseShared方法,该方法在AQS中提供,如下:
public final boolean releaseShared(int arg) {
if (tryReleaseShared(arg)) {
doReleaseShared();
return true;
}
return false;
}
调用tryReleaseShared方法尝试释放锁,如果释放成功,调用doReleaseShared尝试唤醒下一个节点。
AQS的子类需要实现tryReleaseShared方法,Sync中的实现如下:
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;
}
}
从上面可以看到,释放锁的第一步是更新firstReader或HoldCounter的计数,接下来进入死循环,尝试更新AQS的状态,一旦更新成功,则返回;否则,则重试。
释放读锁对读线程没有影响,但是可能会使等待的写线程解除挂起开始运行。所以,一旦没有锁了,就返回true,否则false;返回true后,那么则需要释放等待队列中的线程,这时读线程和写线程都有可能再获得锁。
6. 其他方法
6.1 getOwner()
protected Thread getOwner() {
return sync.getOwner();
}
final Thread getOwner() {
// Must read state before owner to ensure memory consistency
return ((exclusiveCount(getState()) == 0) ?
null :
getExclusiveOwnerThread());
}
getOwner方法用于返回当前获得写锁的线程,如果没有线程占有写锁,那么返回null。如果独占锁的个数为0,说明没有线程占有写锁,那么返回null;否则返回占有写锁的线程。
6.2 getReadLockCount()
getReadLockCount()方法用于返回读锁的个数,实现如下:
public int getReadLockCount() {
return sync.getReadLockCount();
}
Sync的实现如下:
final int getReadLockCount() {
return sharedCount(getState());
}
static int sharedCount(int c) { return c >>> SHARED_SHIFT; }
从上面代码可以看出,要想得到读锁的个数,就是看AQS的state的高16位。这和前面讲过的一样,高16位表示读锁的个数,低16位表示写锁的个数。
6.3 getReadHoldCount()
getReadHoldCount()方法用于返回当前线程所持有的读锁的个数,如果当前线程没有持有读锁,则返回0。直接看Sync的实现即可:
final int getReadHoldCount() {
//如果没有读锁,自然每个线程都是返回0
if (getReadLockCount() == 0)
return 0;
//得到当前线程
Thread current = Thread.currentThread();
//如果当前线程是第一个读线程,返回firstReaderHoldCount参数
if (firstReader == current)
return firstReaderHoldCount;
//如果当前线程不是第一个读线程,得到HoldCounter,返回其中的count
HoldCounter rh = cachedHoldCounter;
//如果缓存的HoldCounter不为null并且是当前线程的HoldCounter,直接返回count
if (rh != null && rh.tid == getThreadId(current))
return rh.count;
//如果缓存的HoldCounter不是当前线程的HoldCounter,那么从ThreadLocal中得到本线程的HoldCounter,返回计数
int count = readHolds.get().count;
//如果本线程持有的读锁为0,从ThreadLocal中移除
if (count == 0) readHolds.remove();
return count;
}
从上面的代码中,可以看到两个熟悉的变量,firstReader和HoldCounter类型。这两个变量在读锁的获取中接触过,前面没有细说,这里细说一下。HoldCounter类的实现如下:
static final class HoldCounter {
int count = 0;
// Use id, not reference, to avoid garbage retention
final long tid = getThreadId(Thread.currentThread());
}
readHolds是ThreadLocalHoldCounter类,定义如下:
static final class ThreadLocalHoldCounter
extends ThreadLocal<HoldCounter> {
public HoldCounter initialValue() {
return new HoldCounter();
}
}
可以看到,readHolds存储了每一个线程的HoldCounter,而HoldCounter中的count变量就是用来记录线程获得的写锁的个数。所以可以得出结论:Sync维持总的读锁的个数,在state的高16位;由于读线程可以同时存在,所以每个线程还保存了获得的读锁的个数,这个是通过HoldCounter来保存的。
除此之外,对于第一个读线程有特殊的处理,Sync中有如下两个变量:
private transient Thread firstReader = null;
private transient int firstReaderHoldCount;
firstReader表示第一个得到读锁的线程,firstReaderHoldCount表示这个线程获得的写锁。所以可以得出结论:第一个获取到读锁的信息保存在firstReader中;其余获取到读锁的线程的信息保存在HoldCounter中。
看完了HoldCounter和firstReader,再来看一下getReadLockCount的实现,主要有三步:
1. 当前没有读锁,那么自然每一个线程获得的读锁都是0;
2. 如果当前线程是第一个获取到读锁的线程,那么返回firstReadHoldCount;
3. 如果当前线程不是第一个获取到读锁的线程,得到该线程的HoldCounter,然后返回其count字段。如果count字段为0,说明该线程没有占有读锁,那么从readHolds中移除。获取HoldCounter分为两步,第一步是与cachedHoldCounter比较,如果不是,则从readHolds中获取。
6.4 getWriteLockCount()
getWriteLockCount()方法返回写锁的个数,Sync的实现如下:
final int getWriteHoldCount() {
return isHeldExclusively() ? exclusiveCount(getState()) : 0;
}
可以看到如果没有线程持有写锁,那么返回0;否则返回AQS的state的低16位。