概述
JDK中提供了一些用于線程之間協(xié)同等待的工具類,CountDownLatch和CyclicBarrier就是最典型的兩個線程同步輔助類。下面分別詳細介紹這兩個類绍申,以及他們之間的異同點。
CountDownLatch類
CountDownLatch顧名思義:倒計數(shù)鎖存器。沒錯凡壤,他就是一個計數(shù)器署尤,并且是倒著計數(shù)的。他的應用場景如下:
一個任務A亚侠,他需要等待其他的一些任務都執(zhí)行完畢之后它才能執(zhí)行曹体。就比如說賽跑的時候,發(fā)令員需要等待所有運動員都準備好了才能發(fā)令硝烂,否則不被K才怪嘞箕别!
此時CountDownLatch就可以大展身手了。
常用操作
本節(jié)介紹CountDownLatch的基本操作函數(shù)滞谢。
構(gòu)造函數(shù)
函數(shù)簽名如下:
public CountDownLatch(int count)
用一個給定的數(shù)值初始化CountDownLatch串稀,之后計數(shù)器就從這個值開始倒計數(shù),直到計數(shù)值達到零狮杨。
await函數(shù)
await函數(shù)用兩種形式母截,簽名分別如下:
public void await() throws InterruptedException
public boolean await(long timeout, TimeUnit unit)
這兩個函數(shù)的作用都是讓線程阻塞等待其他線程,直到CountDownLatch的計數(shù)值變?yōu)?才繼續(xù)執(zhí)行之后的操作橄教。區(qū)別在于第一個函數(shù)沒有等待時間限制清寇,可以等到天荒地老,夯さ枯石爛华烟,第二個函數(shù)給定一個等待超時時間,超過該時間就直接放棄了滓走,并且第二個函數(shù)具有返回值垦江,超時時間之內(nèi)CountDownLatch的值達到0就返回true,等待時間結(jié)束計數(shù)值都還沒達到0就返回false。這兩個操作在等待過程中如果等待的線程被中斷搅方,則會拋出InterruptedException異常比吭。
countDown函數(shù)
這個函數(shù)用來將CountDownLatch的計數(shù)值減一,函數(shù)簽名如下:
public void countDown()
需要說明的是姨涡,如果調(diào)用這個函數(shù)的時候CountDownLatch的計數(shù)值已經(jīng)為0衩藤,那么這個函數(shù)什么也不會做。
getCount函數(shù)
該函數(shù)用來獲取當前CountDownLatch的計數(shù)值涛漂,函數(shù)簽名如下:
public void countDown()
模擬實驗
理論知識講完了赏表,需要真槍實戰(zhàn)地來演示一下這個類的作用,我們就以下面這個場景為例子匈仗,用CountDownLatch來實現(xiàn)這個需求:
有5個運動員賽跑瓢剿,開跑之前,裁判需要等待5個運動員都準備好才能發(fā)令悠轩,并且5個運動員準備好之后也都需要等待裁判發(fā)令才能開跑间狂。
首先分析一下依賴關(guān)系:
裁判發(fā)令 -> 5個運動員都準備好;
5個運動員開跑 -> 裁判發(fā)令火架。
好鉴象,依賴關(guān)系已經(jīng)出來了忙菠,代碼實現(xiàn):
package com.winwill.test;
import java.util.Random;
import java.util.concurrent.CountDownLatch;
/**
* @author qifuguang
* @date 15/8/24 23:35
*/
public class TestCountDownLatch {
private static final int RUNNER_NUMBER = 5; // 運動員個數(shù)
private static final Random RANDOM = new Random();
public static void main(String[] args) {
// 用于判斷發(fā)令之前運動員是否已經(jīng)完全進入準備狀態(tài),需要等待5個運動員纺弊,所以參數(shù)為5
CountDownLatch readyLatch = new CountDownLatch(RUNNER_NUMBER);
// 用于判斷裁判是否已經(jīng)發(fā)令牛欢,只需要等待一個裁判,所以參數(shù)為1
CountDownLatch startLatch = new CountDownLatch(1);
for (int i = 0; i < RUNNER_NUMBER; i++) {
Thread t = new Thread(new Runner((i + 1) + "號運動員", readyLatch, startLatch));
t.start();
}
try {
readyLatch.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
startLatch.countDown();
System.out.println("裁判:所有運動員準備完畢淆游,開始...");
}
static class Runner implements Runnable {
private CountDownLatch readyLatch;
private CountDownLatch startLatch;
private String name;
public Runner(String name, CountDownLatch readyLatch, CountDownLatch startLatch) {
this.name = name;
this.readyLatch = readyLatch;
this.startLatch = startLatch;
}
public void run() {
int readyTime = RANDOM.nextInt(1000);
System.out.println(name + ":我需要" + readyTime + "秒時間準備.");
try {
Thread.sleep(readyTime);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(name + ":我已經(jīng)準備完畢.");
readyLatch.countDown();
try {
startLatch.await(); // 等待裁判發(fā)開始命令
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(name + ":開跑...");
}
}
}
運行結(jié)果如下:
1號運動員:我需要389秒時間準備.
2號運動員:我需要449秒時間準備.
3號運動員:我需要160秒時間準備.
4號運動員:我需要325秒時間準備.
5號運動員:我需要978秒時間準備.
3號運動員:我已經(jīng)準備完畢.
4號運動員:我已經(jīng)準備完畢.
1號運動員:我已經(jīng)準備完畢.
2號運動員:我已經(jīng)準備完畢.
5號運動員:我已經(jīng)準備完畢.
裁判:所有運動員準備完畢傍睹,開始...
1號運動員:開跑...
5號運動員:開跑...
2號運動員:開跑...
4號運動員:開跑...
3號運動員:開跑...
可以看到,一切都是如此地完美稽犁,運動員準備好了之后裁判才發(fā)令焰望,裁判發(fā)令之后運動員才開跑骚亿。
CyclicBarrier類
CyclicBarrier翻譯過來就是:循環(huán)的屏障已亥。什么是循環(huán)?可以重復利用唄来屠,對這個類就是一個可以重復利用的屏障類虑椎。CyclicBarrier主要用于一組固定大小的線程之間,各個線程之間相互等待俱笛,當所有線程都完成某項任務之后捆姜,才能執(zhí)行之后的任務。
如下場景:
有若干個線程都需要向一個數(shù)據(jù)庫寫數(shù)據(jù)迎膜,但是必須要所有的線程都講數(shù)據(jù)寫入完畢他們才能繼續(xù)做之后的事情泥技。
分析一下這個場景的特征:
- 各個線程都必須完成某項任務(寫數(shù)據(jù))才能繼續(xù)做后續(xù)的任務;
- 各個線程需要相互等待磕仅,不能獨善其身珊豹。
這種場景便可以利用CyclicBarrier來完美解決。
常用函數(shù)
本節(jié)介紹CyclicBarrier的基本操作函數(shù)榕订。
構(gòu)造函數(shù)
有兩種類型的構(gòu)造函數(shù)店茶,函數(shù)簽名分別如下:
public CyclicBarrier(int parties, Runnable barrierAction)
public CyclicBarrier(int parties)
參數(shù)parties表示一共有多少線程參與這次“活動”,參數(shù)barrierAction是可選的劫恒,用來指定當所有線程都完成這些必須的“神秘任務”之后需要干的事情贩幻,所以barrierAction這里的動作在一個相互等待的循環(huán)內(nèi)只會執(zhí)行一次。
getParties函數(shù)
getParties用來獲取當前的CyclicBarrier一共有多少線程參數(shù)與两嘴,函數(shù)簽名如下:
public int getParties()
返回參與“活動”的線程個數(shù)丛楚。
await函數(shù)
await函數(shù)用來執(zhí)行等待操作,有兩種類型的函數(shù)簽名:
public int await() throws InterruptedException, BrokenBarrierException
public int await(long timeout, TimeUnit unit)
throws InterruptedException,
BrokenBarrierException,
TimeoutException
第一個函數(shù)是一個無參函數(shù)憔辫,第二個函數(shù)可以指定等待的超時時間趣些。它們的作用是:一直等待知道所有參與“活動”的線程都調(diào)用過await函數(shù),如果當前線程不是即將調(diào)用await函數(shù)的的最后一個線程螺垢,當前線程將會被掛起喧务,直到下列某一種情況發(fā)生:
- 最后一個線程調(diào)用了await函數(shù)逞刷;
- 某個線程打斷了當前線程于置;
- 某個線程打斷了其他某個正在等待的線程;
- 其他某個線程等待時間超過給定的超時時間;
- 其他某個線程調(diào)用了reset函數(shù)骏全。
如果等待過程中線程被打斷了,則會拋出InterruptedException異常河绽;
如果等待過程中出現(xiàn)下列情況中的某一種情況剪芍,則會拋出BrokenBarrierException異常:
- 其他線程被打斷了;
- 當前線程等待超時了玲昧;
- 當前CyclicBarrier被reset了栖茉;
- 等待過程中CyclicBarrier損壞了;
- 構(gòu)造函數(shù)中指定的barrierAction在執(zhí)行過程中發(fā)生了異常孵延。
如果等待時間超過給定的最大等待時間吕漂,則會拋出TimeoutException異常,并且這個時候其他已經(jīng)嗲用過await函數(shù)的線程將會繼續(xù)后續(xù)的動作尘应。
返回值:返回當前線程在調(diào)用過await函數(shù)的所以線程中的編號惶凝,編號為parties-1的表示第一個調(diào)用await函數(shù),編號為0表示是最后一個調(diào)用await函數(shù)犬钢。
isBroken函數(shù)
給函數(shù)用來判斷barrier是否已經(jīng)損壞苍鲜,函數(shù)簽名如下:
public boolean isBroken()
如果因為任何原因被損壞返回true,否則返回false玷犹。
reset函數(shù)
顧名思義混滔,這個函數(shù)用來重置barrier,函數(shù)簽名如下:
public void reset()
如果調(diào)用了該函數(shù)歹颓,則在等待的線程將會拋出BrokenBarrierException異常坯屿。
getNumberWaiting函數(shù)
該函數(shù)用來獲取當前正在等待該barrier的線程數(shù),函數(shù)簽名如下:
public int getNumberWaiting()
模擬實驗
下面用代碼實現(xiàn)下面的場景:
有5個線程都需要向一個數(shù)據(jù)庫寫數(shù)據(jù)晴股,但是必須要所有的線程都講數(shù)據(jù)寫入完畢他們才能繼續(xù)做之后的事情愿伴。
一般情況
代碼:
package com.winwill.test;
import java.util.Random;
import java.util.concurrent.BrokenBarrierException;
import java.util.concurrent.CyclicBarrier;
/**
* @author qifuguang
* @date 15/8/25 00:34
*/
public class TestCyclicBarrier {
private static final int THREAD_NUMBER = 5;
private static final Random RANDOM = new Random();
public static void main(String[] args) {
CyclicBarrier barrier = new CyclicBarrier(THREAD_NUMBER, new Runnable() {
public void run() {
System.out.println(Thread.currentThread().getId() + ":我宣布,所有小伙伴寫入數(shù)據(jù)完畢");
}
});
for (int i = 0; i < THREAD_NUMBER; i++) {
Thread t = new Thread(new Worker(barrier));
t.start();
}
}
static class Worker implements Runnable {
private CyclicBarrier barrier;
public Worker(CyclicBarrier barrier) {
this.barrier = barrier;
}
public void run() {
int time = RANDOM.nextInt(1000);
System.out.println(Thread.currentThread().getId() + ":我需要" + time + "毫秒時間寫入數(shù)據(jù).");
try {
Thread.sleep(time);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getId() + ":寫入數(shù)據(jù)完畢电湘,等待其他小伙伴...");
try {
barrier.await(); // 等待所有線程都調(diào)用過此函數(shù)才能進行后續(xù)動作
} catch (InterruptedException e) {
e.printStackTrace();
} catch (BrokenBarrierException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getId() + ":所有線程都寫入數(shù)據(jù)完畢隔节,繼續(xù)干活...");
}
}
}
運行結(jié)果如下:
10:我需要16毫秒時間寫入數(shù)據(jù).
11:我需要353毫秒時間寫入數(shù)據(jù).
12:我需要101毫秒時間寫入數(shù)據(jù).
13:我需要744毫秒時間寫入數(shù)據(jù).
14:我需要51毫秒時間寫入數(shù)據(jù).
10:寫入數(shù)據(jù)完畢,等待其他小伙伴...
14:寫入數(shù)據(jù)完畢寂呛,等待其他小伙伴...
12:寫入數(shù)據(jù)完畢怎诫,等待其他小伙伴...
11:寫入數(shù)據(jù)完畢,等待其他小伙伴...
13:寫入數(shù)據(jù)完畢贷痪,等待其他小伙伴...
13:我宣布幻妓,所有小伙伴寫入數(shù)據(jù)完畢
13:所有線程都寫入數(shù)據(jù)完畢,繼續(xù)干活...
10:所有線程都寫入數(shù)據(jù)完畢劫拢,繼續(xù)干活...
12:所有線程都寫入數(shù)據(jù)完畢肉津,繼續(xù)干活...
14:所有線程都寫入數(shù)據(jù)完畢强胰,繼續(xù)干活...
11:所有線程都寫入數(shù)據(jù)完畢,繼續(xù)干活...
可以看到妹沙,線程小伙伴們非常團結(jié)偶洋,寫完自己的數(shù)據(jù)之后都在等待其他小伙伴,所有小伙伴都完成之后才繼續(xù)后續(xù)的動作距糖。
重復使用
上面的例子并沒有體現(xiàn)CyclicBarrier可以循環(huán)使用的特點玄窝,所以有如下代碼:
package com.winwill.test;
import java.util.Random;
import java.util.concurrent.BrokenBarrierException;
import java.util.concurrent.CyclicBarrier;
/**
* @author qifuguang
* @date 15/8/25 00:34
*/
public class TestCyclicBarrier {
private static final int THREAD_NUMBER = 5;
private static final Random RANDOM = new Random();
public static void main(String[] args) throws Exception {
CyclicBarrier barrier = new CyclicBarrier(THREAD_NUMBER, new Runnable() {
public void run() {
System.out.println(Thread.currentThread().getId() + ":我宣布,所有小伙伴寫入數(shù)據(jù)完畢");
}
});
for (int i = 0; i < THREAD_NUMBER; i++) {
Thread t = new Thread(new Worker(barrier));
t.start();
}
Thread.sleep(10000);
System.out.println("================barrier重用==========================");
for (int i = 0; i < THREAD_NUMBER; i++) {
Thread t = new Thread(new Worker(barrier));
t.start();
}
}
static class Worker implements Runnable {
private CyclicBarrier barrier;
public Worker(CyclicBarrier barrier) {
this.barrier = barrier;
}
public void run() {
int time = RANDOM.nextInt(1000);
System.out.println(Thread.currentThread().getId() + ":我需要" + time + "毫秒時間寫入數(shù)據(jù).");
try {
Thread.sleep(time);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getId() + ":寫入數(shù)據(jù)完畢悍引,等待其他小伙伴...");
try {
barrier.await(); // 等待所有線程都調(diào)用過此函數(shù)才能進行后續(xù)動作
} catch (InterruptedException e) {
e.printStackTrace();
} catch (BrokenBarrierException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getId() + ":所有線程都寫入數(shù)據(jù)完畢恩脂,繼續(xù)干活...");
}
}
}
運行結(jié)果:
10:我需要228毫秒時間寫入數(shù)據(jù).
11:我需要312毫秒時間寫入數(shù)據(jù).
12:我需要521毫秒時間寫入數(shù)據(jù).
13:我需要720毫秒時間寫入數(shù)據(jù).
14:我需要377毫秒時間寫入數(shù)據(jù).
10:寫入數(shù)據(jù)完畢,等待其他小伙伴...
11:寫入數(shù)據(jù)完畢趣斤,等待其他小伙伴...
14:寫入數(shù)據(jù)完畢俩块,等待其他小伙伴...
12:寫入數(shù)據(jù)完畢,等待其他小伙伴...
13:寫入數(shù)據(jù)完畢唬渗,等待其他小伙伴...
13:我宣布典阵,所有小伙伴寫入數(shù)據(jù)完畢
13:所有線程都寫入數(shù)據(jù)完畢,繼續(xù)干活...
10:所有線程都寫入數(shù)據(jù)完畢镊逝,繼續(xù)干活...
11:所有線程都寫入數(shù)據(jù)完畢,繼續(xù)干活...
14:所有線程都寫入數(shù)據(jù)完畢嫉鲸,繼續(xù)干活...
12:所有線程都寫入數(shù)據(jù)完畢撑蒜,繼續(xù)干活...
================barrier重用==========================
15:我需要212毫秒時間寫入數(shù)據(jù).
16:我需要691毫秒時間寫入數(shù)據(jù).
17:我需要530毫秒時間寫入數(shù)據(jù).
18:我需要758毫秒時間寫入數(shù)據(jù).
19:我需要604毫秒時間寫入數(shù)據(jù).
15:寫入數(shù)據(jù)完畢,等待其他小伙伴...
17:寫入數(shù)據(jù)完畢玄渗,等待其他小伙伴...
19:寫入數(shù)據(jù)完畢座菠,等待其他小伙伴...
16:寫入數(shù)據(jù)完畢,等待其他小伙伴...
18:寫入數(shù)據(jù)完畢藤树,等待其他小伙伴...
18:我宣布浴滴,所有小伙伴寫入數(shù)據(jù)完畢
18:所有線程都寫入數(shù)據(jù)完畢,繼續(xù)干活...
15:所有線程都寫入數(shù)據(jù)完畢岁钓,繼續(xù)干活...
19:所有線程都寫入數(shù)據(jù)完畢升略,繼續(xù)干活...
16:所有線程都寫入數(shù)據(jù)完畢,繼續(xù)干活...
17:所有線程都寫入數(shù)據(jù)完畢屡限,繼續(xù)干活...
可以看到品嚣,barrier的確是重用了。
等待超時
如果await的時候設置了一個最長等待時間钧大,并且等待超時翰撑,則會怎么樣呢?下面的例子故意讓一個線程延遲一段時間才開始寫數(shù)據(jù)啊央,這樣就會出現(xiàn)先等待的線程等待最后一個線程拋出等待超時異常的情況眶诈。
package com.winwill.test;
import java.util.Random;
import java.util.concurrent.BrokenBarrierException;
import java.util.concurrent.CyclicBarrier;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
/**
* @author qifuguang
* @date 15/8/25 00:34
*/
public class TestCyclicBarrier {
private static final int THREAD_NUMBER = 5;
private static final Random RANDOM = new Random();
public static void main(String[] args) throws Exception {
CyclicBarrier barrier = new CyclicBarrier(THREAD_NUMBER, new Runnable() {
public void run() {
System.out.println(Thread.currentThread().getId() + ":我宣布涨醋,所有小伙伴寫入數(shù)據(jù)完畢");
}
});
for (int i = 0; i < THREAD_NUMBER; i++) {
if (i < THREAD_NUMBER - 1) {
Thread t = new Thread(new Worker(barrier));
t.start();
} else { //最后一個線程故意延遲3s創(chuàng)建。
Thread.sleep(3000);
Thread t = new Thread(new Worker(barrier));
t.start();
}
}
}
static class Worker implements Runnable {
private CyclicBarrier barrier;
public Worker(CyclicBarrier barrier) {
this.barrier = barrier;
}
public void run() {
int time = RANDOM.nextInt(1000);
System.out.println(Thread.currentThread().getId() + ":我需要" + time + "毫秒時間寫入數(shù)據(jù).");
try {
Thread.sleep(time);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getId() + ":寫入數(shù)據(jù)完畢逝撬,等待其他小伙伴...");
try {
barrier.await(2000, TimeUnit.MILLISECONDS); // 只等待2s东帅,必然會等待最后一個線程超時
} catch (InterruptedException e) {
e.printStackTrace();
} catch (BrokenBarrierException e) {
e.printStackTrace();
} catch (TimeoutException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getId() + ":所有線程都寫入數(shù)據(jù)完畢,繼續(xù)干活...");
}
}
}
運行結(jié)果:
10:我需要820毫秒時間寫入數(shù)據(jù).
11:我需要140毫秒時間寫入數(shù)據(jù).
12:我需要640毫秒時間寫入數(shù)據(jù).
13:我需要460毫秒時間寫入數(shù)據(jù).
11:寫入數(shù)據(jù)完畢球拦,等待其他小伙伴...
13:寫入數(shù)據(jù)完畢靠闭,等待其他小伙伴...
12:寫入數(shù)據(jù)完畢,等待其他小伙伴...
10:寫入數(shù)據(jù)完畢坎炼,等待其他小伙伴...
java.util.concurrent.BrokenBarrierException
12:所有線程都寫入數(shù)據(jù)完畢愧膀,繼續(xù)干活...
at java.util.concurrent.CyclicBarrier.dowait(CyclicBarrier.java:250)
at java.util.concurrent.CyclicBarrier.await(CyclicBarrier.java:435)
13:所有線程都寫入數(shù)據(jù)完畢,繼續(xù)干活...
11:所有線程都寫入數(shù)據(jù)完畢谣光,繼續(xù)干活...
10:所有線程都寫入數(shù)據(jù)完畢檩淋,繼續(xù)干活...
at com.winwill.test.TestCyclicBarrier$Worker.run(TestCyclicBarrier.java:52)
at java.lang.Thread.run(Thread.java:744)
java.util.concurrent.BrokenBarrierException
at java.util.concurrent.CyclicBarrier.dowait(CyclicBarrier.java:250)
at java.util.concurrent.CyclicBarrier.await(CyclicBarrier.java:435)
at com.winwill.test.TestCyclicBarrier$Worker.run(TestCyclicBarrier.java:52)
at java.lang.Thread.run(Thread.java:744)
java.util.concurrent.TimeoutException
at java.util.concurrent.CyclicBarrier.dowait(CyclicBarrier.java:257)
at java.util.concurrent.CyclicBarrier.await(CyclicBarrier.java:435)
at com.winwill.test.TestCyclicBarrier$Worker.run(TestCyclicBarrier.java:52)
at java.lang.Thread.run(Thread.java:744)
java.util.concurrent.BrokenBarrierException
at java.util.concurrent.CyclicBarrier.dowait(CyclicBarrier.java:250)
at java.util.concurrent.CyclicBarrier.await(CyclicBarrier.java:435)
at com.winwill.test.TestCyclicBarrier$Worker.run(TestCyclicBarrier.java:52)
at java.lang.Thread.run(Thread.java:744)
14:我需要850毫秒時間寫入數(shù)據(jù).
java.util.concurrent.BrokenBarrierException
14:寫入數(shù)據(jù)完畢,等待其他小伙伴...
14:所有線程都寫入數(shù)據(jù)完畢萄金,繼續(xù)干活...
at java.util.concurrent.CyclicBarrier.dowait(CyclicBarrier.java:207)
at java.util.concurrent.CyclicBarrier.await(CyclicBarrier.java:435)
at com.winwill.test.TestCyclicBarrier$Worker.run(TestCyclicBarrier.java:52)
at java.lang.Thread.run(Thread.java:744)
可以看到蟀悦,前面四個線程等待最后一個線程超時了,這個時候他們不再等待最后這個小伙伴了氧敢,而是拋出異常并都繼續(xù)后續(xù)的動作日戈。最后這個線程屁顛屁顛地完成寫入數(shù)據(jù)操作之后也繼續(xù)了后續(xù)的動作。需要說明的是孙乖,發(fā)生了超時異常時候浙炼,還沒有完成“神秘任務”的線程在完成任務之后不會做任何等待,而是會直接執(zhí)行后續(xù)的操作唯袄。
總結(jié)
CountDownLatch和CyclicBarrier都能夠?qū)崿F(xiàn)線程之間的等待弯屈,只不過它們側(cè)重點不同:
- CountDownLatch一般用于某個線程A等待若干個其他線程執(zhí)行完任務之后,它才執(zhí)行恋拷;
- CyclicBarrier一般用于一組線程互相等待至某個狀態(tài)资厉,然后這一組線程再同時執(zhí)行;
- CountDownLatch是不能夠重用的蔬顾,而CyclicBarrier是可以重用的宴偿。
聲明
本文為作者原創(chuàng),也純屬個人見解阎抒,如理解有誤酪我,請留言相告。轉(zhuǎn)載請注明出處: http://qifuguang.me/2015/08/25/[Java并發(fā)包學習五]CountDownLatch和CyclicBarrier介紹
如果你喜歡我的文章且叁,請關(guān)注我的微信訂閱號:“機智的程序猿”都哭,更多精彩,盡在其中:
![](http://7xlune.com1.z0.glb.clouddn.com/%E5%85%AC%E4%BC%97%E5%8F%B7%E4%BA%8C%E7%BB%B4%E7%A0%81.jpg)