目錄
六、AbstractQueuedSynchronizer實現總結
ReentrantReadWriteLock是一個可重入的,支持讀寫分離的鎖,本篇博客就詳細探討該類的實現。
一、定義
ReentrantReadWriteLock用於實現ReadWriteLock接口,該接口定義的方法如下:
該類包含多個內部類,如下:
ReadLock和WriteLock兩個都是基於Sync實現的Lock接口,只是調用Sync的方法不同而已。另外兩個類FairSync和NonfairSync都繼承自Sync,分別表示公平鎖和非公平鎖,其類繼承關係如下:
FairSync和NonfairSync都是隻是實現了父類的兩個抽象方法,writerShouldBlock和readerShouldBlock方法,這兩方法用於判斷寫操作是否需要阻塞,讀操作是否需要阻塞。
Sync內部還有兩個內部類,其定義如下:
HoldCounter是一個數據結構用來記錄每個線程的重入讀鎖次數和關聯的線程ID, ThreadLocalHoldCounter表示一個存儲HoldCounter實例的本地線程變量。多個線程可同時獲取讀鎖,他們獲取的讀鎖實際都是同一個Sync實例,Sync只有一個state屬性用來保存重入讀鎖的總次數,當該次數變成0的時候才能真正釋放讀鎖。因爲獲取讀鎖的線程都需要執行unlock方法釋放鎖,所以必須記錄每個線程自身重入讀鎖的次數,使用ThreadLocalHoldCounter,即下面的readHolds屬性來實現。
Sync中包含的屬性如下:
//本地線程變量,保存當前線程累計重入讀鎖的次數
private transient ThreadLocalHoldCounter readHolds;
//緩存的上一次成功獲取讀鎖的HoldCounter
private transient HoldCounter cachedHoldCounter;
//第一個獲取讀鎖的線程
private transient Thread firstReader = null;
//第一個獲取讀鎖的線程,累計重入讀鎖的次數
private transient int firstReaderHoldCount;
除此之外,Sync定義了幾個常量,用來計算重入讀鎖或者寫鎖的累計次數,如下:
因爲一個線程在獲取寫鎖後可以再獲取讀鎖,爲了在state屬性中同時記錄重入這兩種鎖的次數,Sync只有這一個屬性保存鎖重入次數,就將state屬性拆成兩半,高16位的數值用於表示讀鎖的重入次數,低16位的值表示寫鎖的重入次數,因爲有位數限制,所以重入的最大次數不能超過上面的MAX_COUNT。
ReentrantReadWriteLock本身的屬性如下:
//讀鎖實例
private final ReentrantReadWriteLock.ReadLock readerLock;
//寫鎖實例
private final ReentrantReadWriteLock.WriteLock writerLock;
//完成實際同步動作的Sync實例
final Sync sync;
private static final sun.misc.Unsafe UNSAFE;
//Thread類的tid屬性的偏移量,該屬性實際是關聯的JVM中的JavaThread實例的指針
private static final long TID_OFFSET;
兩個static屬性通過static代碼塊完成初始化,如下:
另外三個屬性是在構造函數中初始化的,構造函數的實現如下:
public ReentrantReadWriteLock() {
this(false);
}
public ReentrantReadWriteLock(boolean fair) {
sync = fair ? new FairSync() : new NonfairSync();
readerLock = new ReadLock(this);
writerLock = new WriteLock(this);
}
下面就以讀鎖和寫鎖的具體實現爲入口來分析內部類Sync的相關實現,因爲其底層都是基於AbstractQueuedSynchronizer,涉及的相關方法可以參考上一篇《Java8 ReentrantLock 源碼解析》。
二、使用
ReentrantReadWriteLock的使用,核心還是基於其writeLock和readLock返回的寫鎖和讀鎖,跟ReentrantLock的使用是一樣,如下:
@Test
public void test() throws Exception {
ReentrantReadWriteLock lock=new ReentrantReadWriteLock();
Lock readLock=lock.readLock();
CyclicBarrier cyclicBarrier=new CyclicBarrier(6);
Runnable a=new Runnable() {
@Override
public void run() {
System.out.println(Thread.currentThread().getName()+" start,time->"+System.currentTimeMillis());
try {
readLock.lock();
Thread.sleep(1000);
System.out.println("do something end for read,time->"+System.currentTimeMillis());
//讀鎖時因爲讀鎖是共享鎖,其他線程都能成功獲取鎖,所以不會出現死鎖問題
cyclicBarrier.await();
}catch (Exception e){
e.printStackTrace();
}finally {
readLock.unlock();
}
}
};
for(int i=0;i<5;i++){
new Thread(a).start();
}
long start=System.currentTimeMillis();
cyclicBarrier.await();
System.out.println("read end,time->"+(System.currentTimeMillis()-start));
Lock writeLock=lock.writeLock();
Runnable b=new Runnable() {
@Override
public void run() {
System.out.println(Thread.currentThread().getName()+" start,time->"+System.currentTimeMillis());
try {
writeLock.lock();
Thread.sleep(1000);
System.out.println("do something end for write,time->"+System.currentTimeMillis());
}catch (Exception e){
e.printStackTrace();
}finally {
//先unlock解鎖,在await等待,避免形成死鎖
writeLock.unlock();
try {
cyclicBarrier.await();
} catch (Exception e) {
e.printStackTrace();
}
}
}
};
for(int i=0;i<5;i++){
new Thread(b).start();
}
start=System.currentTimeMillis();
cyclicBarrier.await();
System.out.println("write end,time->"+(System.currentTimeMillis()-start));
}
上述用例的輸出如下:
//5個讀操作線程依次啓動
Thread-0 start,time->1585987927568
Thread-1 start,time->1585987927568
Thread-3 start,time->1585987927568
Thread-2 start,time->1585987927568
Thread-4 start,time->1585987927569
//幾乎是同一時間獲取讀鎖,然後完成讀操作
do something end for read,time->1585987928568
do something end for read,time->1585987928569
do something end for read,time->1585987928569
do something end for read,time->1585987928569
do something end for read,time->1585987928569
//讀鎖是共享鎖,整體耗時基本等於單個線程耗時,即sleep的耗時
read end,time->1018
//5個寫操作線程依次啓動
Thread-5 start,time->1585987928571
Thread-6 start,time->1585987928571
Thread-7 start,time->1585987928571
Thread-8 start,time->1585987928571
Thread-9 start,time->1585987928571
//寫鎖是排他鎖,5個線程依次獲取鎖
do something end for write,time->1585987929571
do something end for write,time->1585987930571
do something end for write,time->1585987931571
do something end for write,time->1585987932571
do something end for write,time->1585987933571
//總耗時是5個寫操作線程的累計耗時
write end,time->5000
上述用例中讀鎖似乎沒啥價值,因爲不加鎖的效果跟加讀鎖的一樣。讀鎖的價值不在於共享,而在於可以排斥寫鎖,即獲取了讀鎖以後就不能再獲取寫鎖了,必須等讀鎖釋放了才能獲取讀鎖,測試用例如下:
@Test
public void test2() throws Exception {
ReentrantReadWriteLock lock=new ReentrantReadWriteLock();
Lock readLock=lock.readLock();
Lock writeLock=lock.writeLock();
CountDownLatch writeCount=new CountDownLatch(5);
Runnable a=new Runnable() {
@Override
public void run() {
System.out.println(Thread.currentThread().getName()+" start,time->"+System.currentTimeMillis());
try {
readLock.lock();
Thread.sleep(1000);
System.out.println(Thread.currentThread().getName()+" do something end for read,time->"+System.currentTimeMillis());
try{
//寫鎖必須等待所有的讀鎖釋放才能獲取鎖,此處是讀鎖獲取完成,等待獲取寫鎖
//就形成了死鎖
writeLock.lock();
Thread.sleep(1000);
System.out.println(Thread.currentThread().getName()+" do something end for write,time->"+System.currentTimeMillis());
}finally {
writeLock.unlock();
}
}catch (Exception e){
e.printStackTrace();
}finally {
readLock.unlock();
writeCount.countDown();
}
}
};
for(int i=0;i<5;i++){
new Thread(a).start();
}
long start=System.currentTimeMillis();
writeCount.await();
System.out.println("read end,time->"+(System.currentTimeMillis()-start));
}
該用例的輸出如下:
所有線程都卡在獲取寫鎖哪一步了,形成了死鎖。注意獲取寫鎖後,可以再次獲取讀鎖,因爲獲取寫鎖的只能是一個線程,已經獲取了寫鎖再獲取讀鎖不影響讀鎖的語義,測試用例如下:
@Test
public void test3() throws Exception {
ReentrantReadWriteLock lock=new ReentrantReadWriteLock();
Lock readLock=lock.readLock();
Lock writeLock=lock.writeLock();
CountDownLatch writeCount=new CountDownLatch(5);
Runnable a=new Runnable() {
@Override
public void run() {
System.out.println(Thread.currentThread().getName()+" start,time->"+System.currentTimeMillis());
try {
writeLock.lock();
Thread.sleep(1000);
System.out.println(Thread.currentThread().getName()+" do something end for write,time->"+System.currentTimeMillis());
try{
readLock.lock();
Thread.sleep(1000);
System.out.println(Thread.currentThread().getName()+" do something end for read,time->"+System.currentTimeMillis());
}finally {
readLock.unlock();
}
}catch (Exception e){
e.printStackTrace();
}finally {
writeLock.unlock();
writeCount.countDown();
}
}
};
for(int i=0;i<5;i++){
new Thread(a).start();
}
long start=System.currentTimeMillis();
writeCount.await();
System.out.println("write end,time->"+(System.currentTimeMillis()-start));
}
該用例的輸出如下:
5個線程都是按照先獲取寫鎖,獲取讀鎖,釋放讀鎖,釋放寫鎖的順序執行的,最後的總耗時是所有線程兩個sleep的累加時間。
上述場景是一個線程內獲取了寫鎖再獲取讀鎖,如果是不同的線程了?比如線程A佔用寫鎖,此時線程B獲取讀鎖可以獲取成功麼?測試用例如下:
@Test
public void test4() throws Exception {
ReentrantReadWriteLock lock=new ReentrantReadWriteLock();
Lock readLock=lock.readLock();
Lock writeLock=lock.writeLock();
CountDownLatch writeCount=new CountDownLatch(2);
Thread a=new Thread(new Runnable() {
@Override
public void run() {
System.out.println(Thread.currentThread().getName()+" start,time->"+System.currentTimeMillis());
try {
writeLock.lock();
Thread.sleep(2000);
System.out.println(Thread.currentThread().getName()+" do something end for write,time->"+System.currentTimeMillis());
}catch (Exception e){
e.printStackTrace();
}finally {
writeLock.unlock();
writeCount.countDown();
}
}
});
//讓線程a先運行起來,持有寫鎖並sleep
a.start();
Thread.sleep(100);
Thread b=new Thread(new Runnable() {
@Override
public void run() {
System.out.println(Thread.currentThread().getName()+" start,time->"+System.currentTimeMillis());
try {
readLock.lock();
Thread.sleep(2000);
System.out.println(Thread.currentThread().getName()+" do something end for read,time->"+System.currentTimeMillis());
}catch (Exception e){
e.printStackTrace();
}finally {
readLock.unlock();
writeCount.countDown();
}
}
});
b.start();
long start=System.currentTimeMillis();
writeCount.await();
System.out.println("main end,time->"+(System.currentTimeMillis()-start));
}
@Test
public void test5() throws Exception {
ReentrantReadWriteLock lock=new ReentrantReadWriteLock();
Lock readLock=lock.readLock();
Lock writeLock=lock.writeLock();
CountDownLatch writeCount=new CountDownLatch(2);
Thread a=new Thread(new Runnable() {
@Override
public void run() {
System.out.println(Thread.currentThread().getName()+" start,time->"+System.currentTimeMillis());
try {
readLock.lock();
Thread.sleep(2000);
System.out.println(Thread.currentThread().getName()+" do something end for read,time->"+System.currentTimeMillis());
}catch (Exception e){
e.printStackTrace();
}finally {
readLock.unlock();
writeCount.countDown();
}
}
});
//讓線程a先運行起來,持有讀鎖並sleep
a.start();
Thread.sleep(100);
Thread b=new Thread(new Runnable() {
@Override
public void run() {
System.out.println(Thread.currentThread().getName()+" start,time->"+System.currentTimeMillis());
try {
writeLock.lock();
Thread.sleep(2000);
System.out.println(Thread.currentThread().getName()+" do something end for write,time->"+System.currentTimeMillis());
}catch (Exception e){
e.printStackTrace();
}finally {
writeLock.unlock();
writeCount.countDown();
}
}
});
b.start();
long start=System.currentTimeMillis();
writeCount.await();
System.out.println("main end,time->"+(System.currentTimeMillis()-start));
}
上述兩個測試用例的執行結果如下:
//test4方法的輸出
Thread-0 start,time->1585991389197
Thread-1 start,time->1585991389294
Thread-0 do something end for write,time->1585991391197
Thread-1 do something end for read,time->1585991393197
main end,time->3903
//test5方法的輸出
Thread-0 start,time->1585991448174
Thread-1 start,time->1585991448275
Thread-0 do something end for read,time->1585991450174
Thread-1 do something end for write,time->1585991452174
main end,time->3900
總結一下,假如有多個線程,如果A線程佔有讀鎖,其他線程都搶佔同一個讀鎖,搶佔成功,如果搶佔寫鎖,無論是A線程還是其他線程都會搶佔失敗,必須等待讀鎖釋放;如果A線程佔有寫鎖,其他線程無論搶佔讀鎖還是寫鎖都是搶佔失敗,必須等待寫鎖釋放,此時只有A線程自己可以獲取讀鎖或者寫鎖。
三、WriteLock
WriteLock和ReadLock都是實現Lock接口,具體接口的含義可以參考上一篇《Java8 ReentrantLock 源碼解析》,不再重複描述。ReentrantReadWriteLock中也定義了用來獲取等待鏈表狀態的方法如getQueuedThreads,getWaitingThreads,getQueueLength,getWaitQueueLength,其實現和ReentrantLock一致,也不再重複了,重點關注WriteLock和ReadLock的接口實現。注意WriteLock的newCondition接口的實現跟ReentrantLock一樣,返回一個ConditionObject實例,而ReadLock的newCondition接口會拋出UnsupportedOperationException異常。
1、lock / lockInterruptibly
這兩方法都直接依賴於Sync的tryAcquire方法,返回true表示搶佔成功,返回false表示搶佔失敗。
public void lock() {
sync.acquire(1);
}
public void lockInterruptibly() throws InterruptedException {
sync.acquireInterruptibly(1);
}
public final void acquire(int arg) {
//tryAcquire由子類實現,會嘗試獲取鎖,如果獲取失敗返回false,
if (!tryAcquire(arg) &&
//acquireQueued方法會將當前線程阻塞直到獲取鎖爲止,如果返回true表示線程被中斷過
acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
//將當前線程的中斷標識置爲true
selfInterrupt();
}
public final void acquireInterruptibly(int arg)
throws InterruptedException {
if (Thread.interrupted())
throw new InterruptedException();
if (!tryAcquire(arg))
//doAcquireInterruptibly的邏輯和acquireQueued差不多,區別在於前者被中斷後會繼續循環,後者會拋出異常
doAcquireInterruptibly(arg);
}
protected final boolean tryAcquire(int acquires) {
Thread current = Thread.currentThread();
int c = getState();
int w = exclusiveCount(c);
if (c != 0) {
//c不等於0說明鎖已經被佔用了,此時如果w等於0說明此時是讀鎖而非寫鎖,返回false
//判斷是否當前線程佔有鎖,如果不是返回false
if (w == 0 || current != getExclusiveOwnerThread())
return false;
//是當前線程佔有的寫鎖
//如果超過能夠允許的重入的最大次數則拋出異常
if (w + exclusiveCount(acquires) > MAX_COUNT)
throw new Error("Maximum lock count exceeded");
//更新state
setState(c + acquires);
return true;
}
//c等於0,即該鎖未被佔用,如果writerShouldBlock返回true則直接返回false,放入等待獲取鎖的同步鏈表中等待被喚醒
//如果writerShouldBlock返回false,則執行後面的compareAndSetState方法原子修改state
if (writerShouldBlock() ||
!compareAndSetState(c, c + acquires))
return false;
//如果compareAndSetState原子修改state成功,即搶佔鎖成功,則設置當前線程爲佔有鎖的線程
setExclusiveOwnerThread(current);
return true;
}
//取c的低16位的值
static int exclusiveCount(int c) { return c & EXCLUSIVE_MASK; }
2、tryLock
tryLock依賴於Sync的tryWriteLock和tryAcquire方法
public boolean tryLock( ) {
return sync.tryWriteLock();
}
public boolean tryLock(long timeout, TimeUnit unit)
throws InterruptedException {
return sync.tryAcquireNanos(1, unit.toNanos(timeout));
}
//tryWriteLock的實現和tryAcquire的實現基本一致,就是少了一個writerShouldBlock方法的判斷
final boolean tryWriteLock() {
Thread current = Thread.currentThread();
int c = getState();
if (c != 0) {
int w = exclusiveCount(c);
if (w == 0 || current != getExclusiveOwnerThread())
return false;
if (w == MAX_COUNT)
throw new Error("Maximum lock count exceeded");
}
//上述c不等於0的情形也會走到此方法來原子的修改state
if (!compareAndSetState(c, c + 1))
return false;
setExclusiveOwnerThread(current);
return true;
}
public final boolean tryAcquireNanos(int arg, long nanosTimeout)
throws InterruptedException {
if (Thread.interrupted())
throw new InterruptedException();
//tryAcquire是子類實現
return tryAcquire(arg) ||
//如果搶佔失敗,doAcquireNanos將當前線程阻塞直到超時或者獲取鎖
doAcquireNanos(arg, nanosTimeout);
}
3、unlock
unlock依賴於Sync的tryRelease方法
public void unlock() {
sync.release(1);
}
public final boolean release(int arg) {
//tryRelease方法是子類實現,返回true表示成功釋放鎖,返回false表示還不能釋放
if (tryRelease(arg)) {
Node h = head;
if (h != null && h.waitStatus != 0)
//喚醒下一個節點對應的線程
unparkSuccessor(h);
return true;
}
return false;
}
protected final boolean tryRelease(int releases) {
//如果沒有佔有鎖則拋出異常
if (!isHeldExclusively())
throw new IllegalMonitorStateException();
int nextc = getState() - releases;
//判斷重入的計數是否爲0
boolean free = exclusiveCount(nextc) == 0;
if (free)
//計數爲0,將佔有鎖的線程置爲null
setExclusiveOwnerThread(null);
//更新state,就是將其置爲0
setState(nextc);
return free;
}
四、ReadLock
1、lock
lock方法依賴於Sync的tryAcquireShared方法
public void lock() {
sync.acquireShared(1);
}
public final void acquireShared(int arg) {
if (tryAcquireShared(arg) < 0)
doAcquireShared(arg);
}
//返回1表示獲取讀鎖成功,-1表示失敗,需要阻塞當前線程等待其他線程釋放寫鎖
protected final int tryAcquireShared(int unused) {
Thread current = Thread.currentThread();
int c = getState();
//exclusiveCount不等於0,說明有線程佔有了寫鎖,如果佔有寫鎖的線程不是當前線程則返回-1,必須等待該線程釋放寫鎖
if (exclusiveCount(c) != 0 &&
getExclusiveOwnerThread() != current)
return -1;
//exclusiveCount等於0,說明寫鎖未被佔用或者佔用寫鎖的就是當前線程,可以繼續獲取讀鎖
//獲取高16位的值,即重入讀鎖的累計次數
int r = sharedCount(c);
if (!readerShouldBlock() && //readerShouldBlock爲false
r < MAX_COUNT &&
compareAndSetState(c, c + SHARED_UNIT)) { //原子修改state成功
if (r == 0) {
//保存第一個獲取共享鎖的線程
firstReader = current;
firstReaderHoldCount = 1;
} else if (firstReader == current) {
//如果當前線程就是firstReader,計數加1
firstReaderHoldCount++;
} else {
//如果當前線程不是firstReader
//cachedHoldCounter表示上一次獲取共享鎖的線程的HoldCounter
HoldCounter rh = cachedHoldCounter;
//第二個獲取共享鎖的線程進入時,rh就是null,將cachedHoldCounter初始化
//第三個獲取共享鎖的線程進入時,rh的tid就跟當前線程不一致了,更新cachedHoldCounter
if (rh == null || rh.tid != getThreadId(current))
cachedHoldCounter = rh = readHolds.get();
else if (rh.count == 0)
readHolds.set(rh); //當某個線程釋放了讀鎖又重新佔有讀鎖,就可能進入此分支
//當count變成0後會從本地線程變量中移除,此處是將其恢復
//增加計數
rh.count++;
}
return 1;
}
//上述if條件爲false,比如併發修改state,導致compareAndSetState返回false
return fullTryAcquireShared(current);
}
//fullTryAcquireShared的邏輯和tryAcquireShared大體一致,主要多了一個for循環
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 if (readerShouldBlock()) {
// Make sure we're not acquiring read lock reentrantly
if (firstReader == current) {
//正常情況下第一個獲取讀鎖的線程是最後一個釋放的,所以如果當前線程就是第一個獲取讀鎖的線程,則認爲該線程已經佔有讀鎖
} else {
if (rh == null) {
rh = cachedHoldCounter;
//rh不是當前線程的
if (rh == null || rh.tid != getThreadId(current)) {
//賦值成當前線程的
rh = readHolds.get();
if (rh.count == 0) //rh等於0說明該線程已經釋放了佔有的讀鎖,將rh從本地變量表中移除
readHolds.remove();
}
}
//如果count不等於0,說明當前線程還佔有讀鎖,走下面的鎖重入邏輯即可
//count等於0說明當前線程已經完全釋放了讀鎖,此時再獲取鎖,需要進入等待隊列
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
cachedHoldCounter = rh; // cache for release
}
return 1;
}//如果compareAndSetState修改失敗則繼續for循環
}
}
//獲取讀鎖失敗,比如其他的某個線程佔有了寫鎖的情形,必須將當前線程阻塞等待寫鎖釋放
//arg在讀鎖下的值就是1
private void doAcquireShared(int arg) {
//添加一個新的節點到同步鏈表中,主要有以下兩種情形會進入此邏輯
//第一,某個線程佔有了寫鎖,其他線程獲取讀鎖,獲取讀鎖的其他線程就會進入同步鏈表中,其他獲取寫鎖的線程也會進入等待隊列中
//第二,讀鎖被若干線程佔用了,這時某個線程嘗試獲取寫鎖,會進入同步鏈表中,一個新的獲取讀鎖的線程因爲readerShouldBlock返回true,也會進入同步鏈表中
//如果這時又有其他線程嘗試獲取寫鎖,也會進入同步鏈表中,即同步鏈表中既有獲取寫鎖的也有獲取讀鎖的
final Node node = addWaiter(Node.SHARED);
boolean failed = true;
try {
boolean interrupted = false;
for (;;) {
//p可能是獲取寫鎖的節點也可能是獲取讀鎖的節點
final Node p = node.predecessor();
//如果前一個節點是head
if (p == head) {
//嘗試獲取讀鎖
int r = tryAcquireShared(arg);
if (r >= 0) {
//獲取成功,此時r就是1
setHeadAndPropagate(node, r);
p.next = null; // help GC
if (interrupted)
selfInterrupt();
failed = false;
return;
}//如果獲取失敗則繼續蘇塞
}
//如果前一個節點不是head,將當前節點對應的線程阻塞,將前一個節點的狀態修改成SIGNAL
if (shouldParkAfterFailedAcquire(p, node) &&
parkAndCheckInterrupt())
//如果是因爲中斷被喚醒將interrupted置爲true
interrupted = true;
}
} finally {
if (failed)
cancelAcquire(node);
}
}
//獲取高16位的值
static int sharedCount(int c) { return c >>> SHARED_SHIFT; }
private void setHeadAndPropagate(Node node, int propagate) {
Node h = head; // Record old head for check below
//將node置爲head
setHead(node);
if (propagate > 0 || h == null || h.waitStatus < 0 ||
(h = head) == null || h.waitStatus < 0) {
Node s = node.next;
//如果s爲null或者是共享節點
if (s == null || s.isShared())
//喚醒下一個共享節點,如此循環直到緊挨着的所有共享節點都被喚醒了
doReleaseShared();
}
}
2、lockInterruptibly
lockInterruptibly依賴於Sync的tryAcquireShared方法
public final void acquireSharedInterruptibly(int arg)
throws InterruptedException {
if (Thread.interrupted())
throw new InterruptedException();
if (tryAcquireShared(arg) < 0)
doAcquireSharedInterruptibly(arg);
}
//實現和doAcquireShared一致,就是被中斷會拋出異常
private void doAcquireSharedInterruptibly(int arg)
throws InterruptedException {
final Node node = addWaiter(Node.SHARED);
boolean failed = true;
try {
for (;;) {
final Node p = node.predecessor();
if (p == head) {
int r = tryAcquireShared(arg);
if (r >= 0) {
setHeadAndPropagate(node, r);
p.next = null; // help GC
failed = false;
return;
}
}
if (shouldParkAfterFailedAcquire(p, node) &&
parkAndCheckInterrupt())
throw new InterruptedException();
}
} finally {
if (failed)
cancelAcquire(node);
}
}
3、tryLock
tryLock依賴於Sync的tryReadLock和tryAcquireShared方法
public boolean tryLock() {
return sync.tryReadLock();
}
final boolean tryReadLock() {
Thread current = Thread.currentThread();
for (;;) {
int c = getState();
//如果是其他線程佔有了寫鎖,此時無法獲取讀鎖,返回false
if (exclusiveCount(c) != 0 &&
getExclusiveOwnerThread() != current)
return false;
int r = sharedCount(c);
//重入次數達到最大值,拋出異常
if (r == MAX_COUNT)
throw new Error("Maximum lock count exceeded");
//原子的修改state,修改失敗則重試,修改成功返回true
if (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 true;
}
}
}
public boolean tryLock(long timeout, TimeUnit unit)
throws InterruptedException {
return sync.tryAcquireSharedNanos(1, unit.toNanos(timeout));
}
public final boolean tryAcquireSharedNanos(int arg, long nanosTimeout)
throws InterruptedException {
if (Thread.interrupted())
throw new InterruptedException();
return tryAcquireShared(arg) >= 0 ||
doAcquireSharedNanos(arg, nanosTimeout);
}
//該方法的邏輯和doAcquireShared一致,就是增加了超時時間的判斷,如果超時則返回false
private boolean doAcquireSharedNanos(int arg, long nanosTimeout)
throws InterruptedException {
if (nanosTimeout <= 0L)
return false;
final long deadline = System.nanoTime() + nanosTimeout;
final Node node = addWaiter(Node.SHARED);
boolean failed = true;
try {
for (;;) {
final Node p = node.predecessor();
if (p == head) {
int r = tryAcquireShared(arg);
if (r >= 0) {
setHeadAndPropagate(node, r);
p.next = null; // help GC
failed = false;
return true;
}
}
//重新計算剩餘時間
nanosTimeout = deadline - System.nanoTime();
if (nanosTimeout <= 0L)
return false;
if (shouldParkAfterFailedAcquire(p, node) &&
nanosTimeout > spinForTimeoutThreshold)
//剩餘時間較長則park,否則通過for循環自旋
LockSupport.parkNanos(this, nanosTimeout);
if (Thread.interrupted())
throw new InterruptedException();
}
} finally {
if (failed)
cancelAcquire(node);
}
}
4、unlock
unlock基於Sync的tryReleaseShared方法
public void unlock() {
sync.releaseShared(1);
}
public final boolean releaseShared(int arg) {
//tryReleaseShared嘗試釋放鎖,返回true表示需要釋放鎖,false表示還有unlock未調用,不能釋放
if (tryReleaseShared(arg)) {
//喚醒等待獲取讀鎖的線程
doReleaseShared();
return true;
}
return false;
}
protected final boolean tryReleaseShared(int unused) {
Thread current = Thread.currentThread();
if (firstReader == current) {
//如果當前線程是第一個獲取讀鎖的線程
if (firstReaderHoldCount == 1)
firstReader = null;
else
//計數減1
firstReaderHoldCount--;
} else {
HoldCounter rh = cachedHoldCounter;
//如果rh爲null 或者不是當前線程的,則從readHolds中獲取當前線程對應的HoldCounter
if (rh == null || rh.tid != getThreadId(current))
rh = readHolds.get();
int count = rh.count;
if (count <= 1) {
//count等於1,再減就變成0,將其從ThreadLocalMap中移除
readHolds.remove();
//count正常不可能小於等於0,如果某個線程的unlock方法調用次數比lock方法多,則會出現此情形,拋出異常
if (count <= 0)
throw unmatchedUnlockException();
}
//將計數減1
--rh.count;
}
//修改state
for (;;) {
int c = getState();
int nextc = c - SHARED_UNIT;
if (compareAndSetState(c, nextc))
//如果變成0了,則返回true,注意此處是state變成0而不是rh的count變成0
//count變成0只是當前線程不再佔有讀鎖,但是其他線程還在佔有讀鎖
//需要等待所有佔有讀鎖的線程的count都變成,nextc纔會變成0,然後釋放讀鎖
return nextc == 0;
}
}
private void doReleaseShared() {
for (;;) {
Node h = head;
if (h != null && h != tail) {
int ws = h.waitStatus;
//ws等於SIGNAL說明該節點後面還有等待的節點
if (ws == Node.SIGNAL) {
if (!compareAndSetWaitStatus(h, Node.SIGNAL, 0))
continue; //原子的修改成0,修改失敗重試
unparkSuccessor(h); //修改成功喚醒下一個節點對應的線程,被喚醒後就會更改head
}
//ws等於0說明該節點就是最後一個節點了,將其狀態置爲PROPAGATE,然後退出循環
else if (ws == 0 &&
!compareAndSetWaitStatus(h, 0, Node.PROPAGATE))
continue; // 原子的修改成PROPAGATE,修改失敗重試
}
if (h == head) // 如果head改變了則繼續循環,否則終止
break;
}
}
五、NonfairSync / FairSync
這兩個的實現如下:
static final class FairSync extends Sync {
private static final long serialVersionUID = -2274990926593161451L;
final boolean writerShouldBlock() {
return hasQueuedPredecessors();
}
final boolean readerShouldBlock() {
return hasQueuedPredecessors();
}
}
//返回同步鏈表中是否有待處理的不是當前線程的節點
public final boolean hasQueuedPredecessors() {
Node t = tail; // Read fields in reverse initialization order
Node h = head;
Node s;
return h != t &&
//head的next屬性正常不爲null
((s = h.next) == null || s.thread != Thread.currentThread());
}
static final class NonfairSync extends Sync {
final boolean writerShouldBlock() {
return false; // writers can always barge
}
final boolean readerShouldBlock() {
return apparentlyFirstQueuedIsExclusive();
}
}
//head的下一個節點是等待獲取寫鎖的節點
final boolean apparentlyFirstQueuedIsExclusive() {
Node h, s;
return (h = head) != null &&
(s = h.next) != null &&
!s.isShared() &&
s.thread != null;
}
對於寫鎖,公平鎖保證只要同步鏈表中有待處理的節點則將新的獲取寫鎖的節點加入到同步鏈表的末尾,從而實現最早加入鏈表中的節點優先獲取鎖;而非公平鎖下,一個新的獲取寫鎖的線程會與準備喚醒的線程競爭鎖,如果獲取鎖則返回,即不用添加到同步鏈表中。
對於讀鎖,公平鎖的處理跟寫鎖一直;非公平鎖下,只有當同步鏈表中head的下一個節點是獲取寫鎖對應的節點才返回true,這樣可保證如果寫鎖優先於讀鎖被獲取。
六、AbstractQueuedSynchronizer實現總結
AbstractQueuedSynchronizer維護了兩個鏈表,一個由Node的prev,next屬性構成的等待獲取鎖的同步鏈表(簡稱Sync鏈表),鏈表頭是head,鏈表尾是tail,head和tail都是AbstractQueuedSynchronizer的實例屬性,該鏈表是一個雙向鏈表,可以通過head和next屬性往後遍歷,通過tail和prev屬性往前遍歷;另一個是由Node的nextWaiter屬性構成的單向鏈表(簡稱Condition鏈表),該鏈表的鏈表頭是ConditionObject的firstWaiter屬性,鏈表尾是ConditionObject的lastWaiter屬性。
搶佔鎖失敗的時候就會用當前線程初始化一個新的Node節點插入到Sync鏈表末尾,即插入到原tail節點的後面,新節點作爲tail,注意如果鏈表是空的,即head和tail屬性爲空,則通過無參構造函數創建一個空的Node節點,將其賦值給head和tail。如果是搶佔互斥鎖(寫鎖)則將Node設爲EXCLUSIVE模式,nextWaiter屬性爲null,如果是搶佔共享鎖(讀鎖)則Node設爲SHARED模式,nextWaiter屬性爲一個名爲SHARED的static final 的Node實例。注意剛加入到Sync鏈表中的Node節點的waitStatus爲初始值0。
當某個鎖被釋放了,則會喚醒當前head節點的下一個節點簡稱next節點,如果是公平鎖則此時next節點直接獲取鎖,如果是默認的非公平鎖則此時的next節點可能與一個調用lock方法的線程搶佔鎖,如果搶佔失敗則next節點繼續阻塞,鎖釋放時會再次喚醒next節點並嘗試搶佔鎖。如果next節點搶佔成功,則將next節點置爲head節點,thread和prev屬性被置爲null,原來的head節點的next屬性置爲null,即徹底從Sync鏈表中移除了,方便垃圾回收。注意如果next節點是獲取讀鎖的,next節點被置爲head節點後,還需要判斷下一個節點是否也是獲取讀鎖的,如果是則將其喚醒,同樣的被喚醒的獲取讀鎖的線程也會喚醒下一個獲取讀鎖的線程,如此循環直到下一個節點是獲取寫鎖的。這樣做是因爲獲取讀鎖的節點,不需要等待上一個獲取讀鎖的節點釋放鎖了才能搶佔鎖,而是可以直接獲取鎖。
當在某個ConditionObject實例上調用await方法時,會用當前線程創建一個新的Node節點,並將其插入到lastWaiter的後面,新節點作爲lastWaiter,然後將當前線程阻塞,注意此時節點的狀態是CONDITION。在同一個ConditionObject實例上調用sinal方法時,會將firstWaiter從Condition鏈表中移除,firstWaiter的nextWaiter作爲新的firstWaiter,然後將原來的firstWaiter節點的狀態原子的修改成0,將其插入到Sync鏈表尾部。插入後如果此時寫鎖未被佔用或者準備釋放了,則喚醒該節點對應的線程嘗試獲取鎖,如果搶佔失敗則繼續阻塞,直到搶佔成功爲止。如果一個因爲await方法處於阻塞狀態的線程被執行interrupt方法中斷了,該線程被喚醒,然後將節點狀態原子的修改成0並插入到Sync鏈表尾部,嘗試獲取鎖,如果搶佔失敗則繼續阻塞。
最後,總結下Node節點的狀態變化,如下:
- 剛添加到Sync鏈表時,waitStatus爲0,如果搶佔鎖成功,則狀態不變;如果前面有等待的節點或者搶佔失敗,將前一個節點的狀態改成SIGNAL,並阻塞當前線程;即waitStatus爲0表示沒有下一個節點,waitStatus爲SIGNAL表示還有下一個等待被喚醒的節點。當互斥鎖被釋放時,該節點的狀態又會被置爲0,當共享鎖被釋放時,如果有下一個節點則狀態置爲0,否則置爲PROPAGATE。
- 剛添加到Condition鏈表時,waitStatus的狀態是CONDITION,當該節點對應的線程被signal或者線程中斷喚醒時,會將狀態改成0,並加入到Sync鏈表末尾,然後等待被喚醒後搶佔鎖。
- 在等待獲取鎖的過程中如果出現異常,會將Node的狀態置爲CANCELLED,表示一個無效節點,實際處理時會直接跳過這類節點,將他們從鏈表中移除。