CountDownLatch原始碼分析
CountDowntLatch的作用是讓主執行緒等待所有的子執行緒執行完畢之後再進行執行,同時它是基於AQS進行實現的,所以它內部肯定是通過自定義AQS共享模式下的同步器來實現的,該同步器需要重寫AQS提供的tryAcquireShared()以及tryReleaseShared()方法,告訴AQS是否嘗試獲取同步資源以及釋放同步資源成功。
AQS子類需要定義以及維護同步狀態的值,在CountDownLatch中,同步狀態state的值為同步資源的個數。
CountDownLatch的結構
public class CountDownLatch {
/**
* 存在一個AQS共享模式下的同步器
*/
private static final class Sync extends AbstractQueuedSynchronizer {
// ......
}
// 存在一個全域性的同步器屬性
private final Sync sync;
/**
* 構建方法初始化同步器,並指定同步資源的個數
*/
public CountDownLatch(int count) {
if (count < 0) throw new IllegalArgumentException("count < 0");
this.sync = new Sync(count);
}
/**
* 讓主執行緒進行阻塞
*/
public void await() throws InterruptedException {
sync.acquireSharedInterruptibly(1);
}
public boolean await(long timeout, TimeUnit unit)
throws InterruptedException {
return sync.tryAcquireSharedNanos(1, unit.toNanos(timeout));
}
/**
* 讓倒數器-1
*/
public void countDown() {
sync.releaseShared(1);
}
public long getCount() {
return sync.getCount();
}
public String toString() {
return super.toString() + "[Count = " + sync.getCount() + "]";
}
}
可以看到CountDownLatch中定義了一個同步器,然後存在一個全域性的同步器屬性,然後通過構建方法來初始化同步器,並且指定同步器中同步資源的個數。
CountDownLatch的await()方法將會呼叫同步器的acquireSharedInterruptibly()方法,countDown()方法將會呼叫同步器的releaseShared()方法。
剖析同步器
private static final class Sync extends AbstractQueuedSynchronizer {
private static final long serialVersionUID = 4982264981922014374L;
/**
* 構建方法初始化同步資源的個數
*/
Sync(int count) {
setState(count);
}
/**
* 獲取可用的同步資源個數(就是倒數器當前的值)
*/
int getCount() {
return getState();
}
/**
* 嘗試獲取同步資源
*/
protected int tryAcquireShared(int acquires) {
// 只有當同步狀態的值為0,方法才返回true
return (getState() == 0) ? 1 : -1;
}
/**
* 嘗試釋放同步資源
*/
protected boolean tryReleaseShared(int releases) {
// 死迴圈保證CAS操作成功
for (;;) {
int c = getState();
if (c == 0)
return false;
// 讓同步狀態的值-1
int nextc = c-1;
// 只有當執行緒釋放同步資源後,同步狀態的值0時,該方法才會返回true
if (compareAndSetState(c, nextc))
return nextc == 0;
}
}
}
tryAcquireShared()方法用於嘗試獲取同步資源,正常情況下,如果執行緒獲取失敗則返回false,否則返回剩餘的可用資源個數(state - 要獲取的資源個數),但是在CountDownLatch同步器的tryAcquireShared()中,只有當等待狀態的值為0時,方法才返回true,否則返回false。
tryReleaseShared()方法用於嘗試釋放同步資源,正常情況下,將同步狀態的值累加,也就是恢復,然後返回true,但是在CountDownLatch同步器的tryReleaseShared()方法中,並沒有累加同步狀態的值,而是當執行緒每次釋放後,將同步狀態的值-1,只有當執行緒釋放同步狀態後,state的值為0,該方法才會返回true。
流程總結
1.首先建立一個CountDownLatch例項,並指定倒數器的閾值。
2.主執行緒呼叫CountDownLatch的await()方法進行阻塞,該方法呼叫同步器的acquireSharedInterruptibly()方法,該方法內部會呼叫tryAcquireShared()方法,嘗試獲取同步資源,但是在tryAcquireShared()方法中,只有當同步狀態的值為0時,方法才會返回true,由於目前同步狀態的值不為0,因此方法返回false,因此該執行緒將會封裝成Node節點,然後加入到等待佇列當中,該執行緒將會進行阻塞。
3.子執行緒呼叫CountDownLatch的countDown()方法讓倒數器-1,該方法呼叫同步器releaseShared()方法,該方法內部將會呼叫tryReleaseShared()方法,嘗試釋放同步資源,但是在tryReleaseShared()方法中,會將同步狀態的值-1,同時只有當執行緒釋放同步資源後,同步狀態的值為0時,該方法才會返回true,否則返回false,如果tryReleaseShared()方法返回false,那麼就不做任何處理,只有當該方法返回true,也就是所有的子執行緒都執行了countDown()方法,將同步狀態的值設定為0,當該方法返回true時,那麼就會喚醒離頭節點最近的同時等待狀態不為CANCELLED的後繼節點,也就是主執行緒,然後主執行緒呼叫tryAcquireShared()方法嘗試獲取同步資源,由於當前同步狀態的值已經為0,因此tryAcquireShared()方法返回true,然後主執行緒直接返回,做自己的事情。
FAQ
CountDownLatch為什麼不能重用?
不能重用,因此當主執行緒被喚醒後,然後呼叫tryAcquireShared()方法獲取了同步資源,然後就直接返回,做自己的事情,永遠都不會釋放同步資源,因此不能重用。