什麼是CountDownLatch
CountDownLatch與CyclicBarrier一樣,也是一個用與同步的輔助類,它的使用場景是:在一個或者一組其他線程沒有執行完畢之前,使當前線程進行等待,只有其他的線程全部完成執行完畢後,當前線程才能繼續進行。前一篇我們介紹了CyclicBarrier,在這裏說一下CountDownLatch與CyclicBarrier的區別。
CyclicBarrier是執行了CyclicBarrier.await( )方法的幾個線程之間互相等待,當所有的其他線程都執行了await方法時(除了主線程),所有線程繼續執行;而CountDownLatch是一個線程等待一個或者多個其他線程都執行完畢,並且調用了CountDownLatch.countDown( )方法後,才能繼續執行。
CountDownLatch和CyclicBarrier都有一個計數器的概念,但是CyclicBarrier的計數器可以重置繼續使用(有一個 Generation 的概念,可以參考上一篇博客);而CountDownLatch則沒有這個概念,計數器不能被重置,所以不能重複使用。
CountDownLatch使用示例
下面通過一個示例來了解一下CountDownLatch的使用。
/**
* Created by fei on 2017/6/9.
*/
public class CountDownLatchDemo {
public static final int INIT_SIZE = 3;
private static CountDownLatch countDownLatch;
public static void main(String[] args) {
try {
System.out.println("我想結婚");
countDownLatch = new CountDownLatch(INIT_SIZE);
new ThreadDemo("求婚成功").start();
new ThreadDemo("雙方父母點頭了").start();
new ThreadDemo("房子也買好了").start();
countDownLatch.await();
Thread.sleep(1000);
System.out.println("好了,可以去領證了");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
static class ThreadDemo extends Thread {
public ThreadDemo(String name) {
super(name);
}
@Override
public void run() {
try {
Thread.sleep(1000);
System.out.println(Thread.currentThread().getName());
countDownLatch.countDown();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
哈哈,舉了一個小例子,正常操作下,只有這幾個條件都滿足了,也就是這幾個線程(求婚成功、雙方父母點頭了、房子也買好了)都執行完了纔可能去執行領證操作。
我想結婚
求婚成功
房子也買好了
雙方父母點頭了
好了,可以去領證了
從示例的代碼上可以看出來,這個CountDownLatch的使用和CyclicBarrier有異曲同工之妙。具體的,我們再放張圖片來加強理解:
圖中的示例和代碼表達的意思是一樣的,就是利用CountDownLatch來實現不同線程之間的協同。其中,這個 cnt 變量,也就是代碼中的 INIT_SIZE int類型變量是關鍵,它代表的意思就是執行了CountDownLatch.await( )方法的線程需要另外的線程執行幾次 CountDownLatch.countDown( )方法。下面,就根據具體方法的源碼來看一下CountDownLatch是怎樣實現的。
CountDownLatch源碼解析
CountDownLatch的源碼不是很長,我就將去掉註釋的全部源碼展示在這裏:
package java.util.concurrent;
import java.util.concurrent.locks.AbstractQueuedSynchronizer;
public class CountDownLatch {
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) {
return (getState() == 0) ? 1 : -1;
}
protected boolean tryReleaseShared(int releases) {
// Decrement count; signal when transition to zero
for (;;) {
int c = getState();
if (c == 0)
return false;
int nextc = c-1;
if (compareAndSetState(c, nextc))
return nextc == 0;
}
}
}
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));
}
public void countDown() {
sync.releaseShared(1);
}
public long getCount() {
return sync.getCount();
}
public String toString() {
return super.toString() + "[Count = " + sync.getCount() + "]";
}
}
可以看出,CountDownLatch的源碼不是很難,我們從主要的await方法和countDown方法講起。
await方法
public void await() throws InterruptedException {
sync.acquireSharedInterruptibly(1);
}
可以看出,await實際上調用的是AQS的acquireSharedInterruptibly(1)方法,acquireSharedInterruptibly()方法如下:
public final void acquireSharedInterruptibly(int arg)
throws InterruptedException {
if (Thread.interrupted())//如果線程是中斷狀態,則拋出中斷異常
throw new InterruptedException();
if (tryAcquireShared(arg) < 0) //嘗試獲取鎖操作
//如果嘗試獲取鎖失敗,則調用這個方法,會使線程一直不斷的獲取鎖,直到獲取到鎖,
//或者該線程中斷
doAcquireSharedInterruptibly(arg);
}
在上面的全部的源碼中可以看到, tryAcquireShared方法已經被重寫了:
protected int tryAcquireShared(int acquires) {
return (getState() == 0) ? 1 : -1;
}
getState() = 0 就是表示此時的鎖是沒有被佔用的,是可以獲取的狀態。 否則,執行doAcquireSharedInterruptibly( )方法:
private void doAcquireSharedInterruptibly(int arg)
throws InterruptedException {
//創建包裹當前線程的節點,初始化爲“共享鎖”,將Node節點添加到AQS維護的隊列中
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);
}
}
shouldParkAfterFailedAcquire方法
// 根據名字就能猜到這個方法的意思
//獲取鎖失敗後當前結點(線程)是否應該足阻塞
private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) {
// 獲取前一個節點的狀態
int ws = pred.waitStatus;
// 如果前一個節點是SIGNAL狀態,就意味這當前線程應該被unpark喚醒。
//並且這裏返回true。
if (ws == Node.SIGNAL)
return true;
// 如果前一個節點的狀態 > 0
//(這裏代表的是線程已取消狀態,有關Node節點,將在下一個章節中講解)
if (ws > 0) {
//向前尋找,一直找到不爲取消狀態的節點(節點pred)
do {
node.prev = pred = pred.prev;
} while (pred.waitStatus > 0);
pred.next = node;//把當前結點設置爲節點pred的後繼節點
} else {
// 如果前一個節點 < 0,則設置前繼節點爲SIGNAL狀態。
compareAndSetWaitStatus(pred, ws, Node.SIGNAL);
}
return false;
}
可能看到這裏會對waitStatus的狀態有一些不理解,再詳細講解Node節點之前,我們先說一下,這裏的waitStatus都有哪些狀態。
//下面是AQS類中,Node節點的源碼片段,針對的是waitStatus的狀態值
// 線程已被取消
static final int CANCELLED = 1;
// 當前結點(線程)被釋放掉或者取消掉時,它的後繼節點(線程)需要被喚醒
// 這個SIGNAL就可以理解成“我要通知下一個節點可以被喚醒了”
static final int SIGNAL = -1;
// 線程在等待Condition喚醒
static final int CONDITION = -2;
// 其它線程獲取到“共享鎖”
static final int PROPAGATE = -3;
// 值得注意的是,當 waitStatus=0時,意味着當前線程不屬於上面的任何一種狀態。
volatile int waitStatus;
看完Node中關於節點狀態的的解釋,上面的代碼應該容易理解多了。
parkAndCheckInterrupt方法
和shouldParkAfterFailedAcquire方法一樣,parkAndCheckInterrupt也是定義在AQS類中的方法。
private final boolean parkAndCheckInterrupt() {
LockSupport.park(this);
return Thread.interrupted();
}
這個方法很簡單,就是調用LockSupport的park方法,將當前線程進行阻塞(順便提一嘴,JUC包中的線程的阻塞和喚醒,都是調用的LockSupport這個類),然後返回線程的中斷狀態。
countDown方法
public void countDown() {
sync.releaseShared(1);
}
countDown方法調用的是sync的releaseShared()方法:
public final boolean releaseShared(int arg) {
if (tryReleaseShared(arg)) {
doReleaseShared();
return true;
}
return false;
}
可以看出,releaseShared方法先是調用tryReleaseShared方法獲取鎖,如果獲取失敗,則調用doReleaseShared方法再進行獲取鎖操作。(大神不愧是大神!其實doReleaseShared方法中調用的還是tryReleaseShared方法,爲啥要這樣寫?讓我寫可能就直接硬生生的去做獲取鎖操作了)
這裏的tryReleaseShared被CountDownLatch覆蓋了:
protected boolean tryReleaseShared(int releases) {
// Decrement count; signal when transition to zero
for (;;) {
int c = getState();
if (c == 0)
return false;
int nextc = c-1;
// 調用CAS函數進行賦值。
if (compareAndSetState(c, nextc))
return nextc == 0;
}
}
這個方法也很好理解,就是針對 state 進行減操作。
源碼分析總結
其實看完countDown方法就應該可以看出CountDownLatch具體的設計與使用思路了,就是使用CountDownLatch的時候,先進行”鎖計數器”(也就是 INIT_SIZE 參數,或者更簡單的說是CountDownLatch這個類的“Count”),這個數值很關鍵,它決定了調用CountDownLatch.await()方法的線程想要繼續運行下去,則需要多少次調用CountDownLatch.countDown()方法。當調countDown方法時,鎖計數器減 1 ,知道所計數器爲 0 時,執行CountDownLatch.await()的線程才能獲取到鎖,從而繼續執行。