jdk1.5開始concurrent包裏提供的,併發編程工具類。 node
CountDownLatch這個類可以使一個線程等待其餘線程完成各自的工做後再執行。CountDownLatch容許一個或多個線程等待其餘線程完成操做。 算法
例如,應用程序的主線程但願在負責啓動框架服務的線程已經啓動全部的框架服務以後再執行。編程
CountDownLatch很是適合於對任務進行拆分,使其並行執行,好比某個任務執行2s,其對數據的請求能夠分爲五個部分,那麼就能夠將這個任務拆分爲5個子任務,分別交由五個線程執行,執行完成以後再由主線程進行彙總,此時,總的執行時間將決定於執行最慢的任務,平均來看,仍是大大減小了總的執行時間。bash
CountDownLatch是不能複用的,不可能從新初始化或者修改CountDownLatch對象的內部計數器的值。
多線程
public void countDown() {
sync.releaseShared(1);
}複製代碼
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) {
for (;;) {
int c = getState(); // 獲取當前state屬性的值
if (c == 0) // 若是state爲0,則說明當前計數器已經計數完成,直接返回
return false;
int nextc = c-1;
if (compareAndSetState(c, nextc)) // 使用CAS算法對state進行設置
return nextc == 0; // 設置成功後返回當前是否爲最後一個設置state的線程
}
}
}複製代碼
public final boolean releaseShared(int arg) {
if (tryReleaseShared(arg)) {
doReleaseShared();
return true;
}
return false;
}複製代碼
private void doReleaseShared() {
for (;;) {
Node h = head; // 記錄等待隊列中的頭結點的線程
if (h != null && h != tail) { // 頭結點不爲空,且頭結點不等於尾節點
int ws = h.waitStatus;
if (ws == Node.SIGNAL) { // SIGNAL狀態表示當前節點正在等待被喚醒
if (!compareAndSetWaitStatus(h, Node.SIGNAL, 0)) // 清除當前節點的等待狀態
continue;
unparkSuccessor(h); // 喚醒當前節點的下一個節點
} else if (ws == 0 && !compareAndSetWaitStatus(h, 0, Node.PROPAGATE))
continue;
}
if (h == head) // 若是h仍是指向頭結點,說明前面這段代碼執行過程當中沒有其餘線程對頭結點進行過處理
break;
}
}複製代碼
private void unparkSuccessor(Node node) {
int ws = node.waitStatus;
if (ws < 0)
compareAndSetWaitStatus(node, ws, 0); // 清除當前節點的等待狀態
Node s = node.next;
if (s == null || s.waitStatus > 0) { // s的等待狀態大於0說明該節點中的線程已經被外部取消等待了
s = null;
// 從隊列尾部往前遍歷,找到最後一個處於等待狀態的節點,用s記錄下來
for (Node t = tail; t != null && t != node; t = t.prev)
if (t.waitStatus <= 0)
s = t;
}
if (s != null)
LockSupport.unpark(s.thread); // 喚醒離傳入節點最近的處於等待狀態的節點線程
}複製代碼
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);
}複製代碼
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) { // 大於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);
}
}複製代碼
private void setHeadAndPropagate(Node node, int propagate) {
Node h = head;
setHead(node); // 將當前節點設置爲頭節點
// 檢查喚醒過程是否須要往下傳遞,而且檢查頭結點的等待狀態
if (propagate > 0 || h == null || h.waitStatus < 0 ||
(h = head) == null || h.waitStatus < 0) {
Node s = node.next;
if (s == null || s.isShared()) // 若是下一個節點是嘗試以共享狀態獲取獲取執行權限的節點,則將其喚醒
doReleaseShared();
}
}複製代碼
public class CountdownLatchExample {
public static void main(String[] args) throws InterruptedException {
final int totalThread = 10;
CountDownLatch countDownLatch = new CountDownLatch(totalThread);
ExecutorService executorService = Executors.newCachedThreadPool();
for (int i = 0; i < totalThread; i++) {
executorService.execute(() -> {
System.out.print("run..");
countDownLatch.countDown();
});
}
countDownLatch.await();
System.out.println("end");
executorService.shutdown();
}
}
run..run..run..run..run..run..run..run..run..run..end複製代碼
CountDownLatch的做用就是容許一個或多個線程等待其餘線程完成操做,看起來有點相似join() 方法,但其提供了比 join() 更加靈活的API。CountDownLatch能夠手動控制在n個線程裏調用n次countDown方法使計數器進行減一操做,也能夠在一個線程裏調用n次執行減一操做。而 join() 的實現原理是不停檢查join線程是否存活,若是 join 線程存活則讓當前線程永遠等待。因此二者之間相對來講仍是CountDownLatch使用起來較爲靈活。併發
參考自:《Java併發編程的藝術》和www.jianshu.com/p/128476015…框架