目錄
3 基於AbstractQueuedSynchronizer的實現
1 前言
本人使用jdk8版本。
CountDownLatch
允許一個或多個線程等待其他線程完成操作,作用與Thread.join()相似,join可以參考:Thread.join()原理。
2 功能介紹
在JDK 1.5之後的併發包中提供的 CountDownLatch
也可以實現 join
的功能,並且比join
的功能更多,示例如下:
public class CountDownLatchTest {
static CountDownLatch c=new CountDownLatch(2);
public static void main(String[] args) throws InterruptedException{
new Thread(new Runnable(){
@Override
public void run(){
System.out.println(1);
c.countDown();
System.out.println(2);
c.countDown();
}
}).start();
c.await();
System.out.println("3");
}
}
上面代碼輸出結果爲1,2,3。main線程會一直阻塞在c.await()上,直到線程中兩次c.countDown()使構造方法中傳入的2變成0。
CountDownLatch
的構造函數接收一個int
類型的參數作爲計數器,如果你想等待N
個點完成,這裏就傳入N
。
當我們調用 CountDownLatch
的 countDown
方法時,N
就會 -1
,CountDownLatch
的 await
方法會阻塞當前線程,直到 N 變成零
。
由於 countDown
方法可以用在任何地方,所以這裏說的 N
個點,可以是 N個線程,也可以是 1個線程裏的N個執行步驟。用在多個線程時,只需要把這個 CountDownLatch
的引用傳遞到線程裏即可。
如果有某個解析 sheet
的線程處理得比較慢,我們不可能讓主線程一直等待,所以可以使用另外一個帶指定時間的 await
方法—— await(long time,TimeUnit unit)
,這個方法等待特定時間後,就會不再阻塞當前線程。
3 基於AbstractQueuedSynchronizer的實現
CountDownLatch有一個靜態內部類sync,它是AbstractQueuedSynchronizer子類,並針對自己的功能重寫了AbstractQueuedSynchronizer的構造方法、getCount()、tryAcquireShared()、tryReleaseShared()。CountDownLatch.await()調用的是同步器的acquireSharedInterruptibly(),CountDownLatch.countDown()調用的是同步器的releaseShared()。
可以參考:AbstractQueuedSynchronizer共享式同步狀態獲取與釋放。
3.1 同步器Sync
private final Sync sync;
private static final class Sync extends AbstractQueuedSynchronizer {
private static final long serialVersionUID = 4982264981922014374L;
Sync(int count) {
setState(count); // 設置鎖被獲取的個數
}
int getCount() {
return getState(); // 獲取鎖被獲取的個數
}
// 若鎖的獲取數位0時獲取成功,否則獲取失敗且當前線程阻塞
protected int tryAcquireShared(int acquires) {
return (getState() == 0) ? 1 : -1;
}
// 將鎖的獲取數-1,當減至0時返回true且阻塞的線程會被喚醒,否則false
protected boolean tryReleaseShared(int releases) {
for (;;) {
int c = getState();
if (c == 0)
return false;
int nextc = c-1;
if (compareAndSetState(c, nextc))
return nextc == 0;
}
}
}
3.2 構造方法
初始化Sync屬性,同時設置鎖已被獲取的個數。
public CountDownLatch(int count) {
if (count < 0) throw new IllegalArgumentException("count < 0");
this.sync = new Sync(count);
}
3.3 await方法
acquireSharedInterruptibly()會先調用重寫的tryAcquireShared()來嘗試獲取鎖,若鎖的數量>0,會阻塞在該方法,直到鎖的數量=0將當前線程從該方法喚醒。
public void await() throws InterruptedException {
sync.acquireSharedInterruptibly(1);
}
public final void acquireSharedInterruptibly(int arg)
throws InterruptedException {
if (Thread.interrupted())
throw new InterruptedException();
if (tryAcquireShared(arg) < 0)
doAcquireSharedInterruptibly(arg);
}
3.4 countDown方法
countDown()會嘗試調用重寫的tryReleaseShared()來釋放鎖,只有getCount() == 0時返回true,進而執行doReleaseShared()來將阻塞的線程喚醒。
public void countDown() {
sync.releaseShared(1);
}
public final boolean releaseShared(int arg) {
if (tryReleaseShared(arg)) {
doReleaseShared();
return true;
}
return false;
}
4 一次性使用
從上面介紹可知,要想使用CountDownLatch完成相關功能,首先要在構造時傳入一個int值,即Sync同步器鎖的狀態,當功能實現後,Sync鎖的狀態肯定是0,否則線程會一直阻塞。CountDownLatch中除了構造方法中可設置Sync鎖的狀態外沒有提供其它任何方法來設置,加上Sync屬性是private的,所以無法從外部訪問。
因此,在使用CountDownLatch後,建議將指向CountDownLatch的引用設爲null,這樣方便GC。
5 阿里面試題
1.countdowlatch和cyclicbarrier的內部原理和用法,以及相互之間的差別(比如countdownlatch的await方法和countDown是怎麼實現的)。