CountDownLatch至關於一個門閂,門閂上掛了N把鎖。只有N把鎖都解開的話,門纔會打開。怎麼理解呢?我舉一個賽跑比賽的例子,賽跑比賽中必須等待全部選手都準備好了,裁判才能開發令槍。選手才能夠開始跑。CountDownLatch當中主要有兩個方法,一個是await()會掛上鎖阻塞當前線程,至關於裁判站在起始點等待,等待各位選手準備就緒,一個是countDown方法用於解鎖,至關於選手準備好了以後調用countDown方法告訴裁判本身準備就緒,當全部人都準備好了以後裁判開發令槍。bash
public class TestCountDownLatch {
public static void main(String[] args) {
// 須要等待兩個線程,因此傳入參數爲2
CountDownLatch latch = new CountDownLatch(2);
// 該線程運行1秒
new Thread(new Runnable() {
@Override
public void run() {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("1號選手準備就緒!用時1秒!");
latch.countDown();
}
}).start();
// 該線程運行3秒
new Thread(new Runnable() {
@Override
public void run() {
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("2號選手準備就緒!用時3秒!");
latch.countDown();
}
}).start();
try {
System.out.println("請1號選手和2號選手各就各位!");
// 主線程在此等待兩個線程執行完畢以後繼續執行
latch.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
// 兩個線程執行完畢後,主線程恢復運行
System.out.println("裁判發槍,1號選手和2號選手開跑!");
}
}
複製代碼
請1號選手和2號選手各就各位!
1號選手準備就緒!用時1秒!
2號選手準備就緒!用時3秒!
裁判發槍,1號選手和2號選手開跑!
複製代碼
若是去掉CountDownLatch的效果呢?運行結果就會變成以下:多線程
請1號選手和2號選手各就各位!
裁判發槍,1號選手和2號選手開跑!
1號選手準備就緒!用時1秒!
2號選手準備就緒!用時3秒!
複製代碼
裁判就會在選手還未準備就緒的時候開發令槍,這就亂套了。
其實CountDownLatch一個最簡單的用處就是計算多線程執行完畢時的時間。像剛纔的例子當中兩個線程並行執行了共花費了3秒鐘。ide
CyclicBarrier就像一個柵欄,將各個線程攔住。Cyclic是循環的英文,代表該工具能夠進行循環使用。CyclicBarrier(N)的構造參數代表該一共有幾個線程須要互相等待。它至關於N個選手約定進行屢次比賽,每次比賽完都要在起跑點互相等待。讀者可能會立刻疑惑這不是和CountDownLatch同樣嗎?不同。由於CountDownLatch是裁判等待選手,是調用await()方法的線程,等待調用countDown()方法的各個線程。而CyclicBarrier是選手等待選手,是調用await()方法的線程互相等待,等待其餘線程都運行好以後,再開始下一輪運行。工具
咱們舉一個例子,兩個選手進行比賽,一共進行三輪比賽。ui
public class TestCyclicBarrier {
// 1號選手跑的輪數
public static int countA = 1;
// 2號選手跑的輪數
public static int countB = 1;
public static void main(String[] args) {
// 填入2,表明2個線程互相等待
CyclicBarrier barrier = new CyclicBarrier(2);
new Thread(new Runnable() {
@Override
public void run() {
// 一共跑三輪
for (int i = 0; i < 3; i++) {
System.out.println("1號選手開始跑!當前第" + countA++ + "輪比賽!");
// 1號選手跑得慢,每次跑三秒
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
try {
System.out.println("1號選手抵達終點!");
// 調用等待方法,在此等待其餘選手
barrier.await();
} catch (InterruptedException e) {
e.printStackTrace();
} catch (BrokenBarrierException e) {
e.printStackTrace();
}
}
}
}).start();
new Thread(new Runnable() {
@Override
public void run() {
// 一共等待三輪
for (int i = 0; i < 3; i++) {
System.out.println("2號選手開始跑!當前第" + countB++ + "輪比賽!");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
try {
System.out.println("2號選手抵達終點!");
// 調用等待方法,在此等待其餘選手
barrier.await();
} catch (InterruptedException e) {
e.printStackTrace();
} catch (BrokenBarrierException e) {
e.printStackTrace();
}
}
}
}).start();
}
}
複製代碼
1號選手開始跑!當前第1輪比賽!
2號選手開始跑!當前第1輪比賽!
2號選手抵達終點!
1號選手抵達終點!
1號選手開始跑!當前第2輪比賽!
2號選手開始跑!當前第2輪比賽!
2號選手抵達終點!
1號選手抵達終點!
1號選手開始跑!當前第3輪比賽!
2號選手開始跑!當前第3輪比賽!
2號選手抵達終點!
1號選手抵達終點!
複製代碼
每輪比賽1號選手和2號選手都會回到起跑線互相等待,再開啓下一輪比賽。
若是不加CyclicBarrier呢?spa
1號選手開始跑!當前第1輪比賽!
2號選手開始跑!當前第1輪比賽!
2號選手抵達終點!
2號選手開始跑!當前第2輪比賽!
2號選手抵達終點!
2號選手開始跑!當前第3輪比賽!
1號選手抵達終點!
1號選手開始跑!當前第2輪比賽!
2號選手抵達終點!
1號選手抵達終點!
1號選手開始跑!當前第3輪比賽!
1號選手抵達終點!
複製代碼
此時2號選手就直接跑完三輪比賽,不等1號選手了。線程
Semaphore英文的字面意思是信號量。它的工做機制是每一個線程想要獲取運行的機會的話,都必須獲取到信號量。acquire()方法阻塞的獲取信號量,release()釋放信號量。舉個例子,假設咱們去迪士尼遊玩,可是迪士尼擔憂遊客不少的話,影響你們的遊玩體驗,因而規定每一個小時只能賣出兩張門票。這樣就能夠控制在遊樂園當中的遊客數量了。code
public class TestSemaphore {
public static void main(String[] args) {
Semaphore semaphore = new Semaphore(0);
System.out.println("顧客在售票處等候中");
new Thread(new Runnable() {
@Override
public void run() {
for (; ; ) {
try {
Thread.sleep(500);
// 等待出票
semaphore.acquire();
System.out.println("顧客拿到門票入場!");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}).start();
new Thread(new Runnable() {
@Override
public void run() {
for (int i = 0; i < 3; i++) {
try {
// 等待一小時再發門票
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
// 一次性發出兩張門票
System.out.println("售票處第" + (i + 1) + "小時售出兩張票!");
semaphore.release();
semaphore.release();
}
}
}).start();
System.out.println("售票處開始售票!");
}
}
複製代碼
顧客在售票處等候中...
售票處開始售票!
售票處第1小時售出兩張票!
顧客拿到門票入場!
顧客拿到門票入場!
售票處第2小時售出兩張票!
顧客拿到門票入場!
顧客拿到門票入場!
售票處第3小時售出兩張票!
顧客拿到門票入場!
顧客拿到門票入場!
複製代碼
Exchanger提供了讓兩個線程互相交換數據的同步點。Exchanger有點像2個線程的CyclicBarrier,線程之間都是互相等待,區別在於Exchanger多了交換的操做。舉個例子比如之前玩網遊的時候,買家和賣家必須走到地圖上同一地點面對面進行交易同樣,一手交錢一手交裝備。遊戲
public class TestExchanger {
public static void main(String[] args) {
Exchanger<String> exchanger = new Exchanger<>();
new Thread(new Runnable() {
@Override
public void run() {
String weapon = "裝備";
System.out.println("我是賣家,我帶着" + weapon + "過來了!");
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("賣家到達地圖上交易地點");
try {
System.out.println("我是賣家,換回了" + exchanger.exchange(weapon));
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}).start();
new Thread(new Runnable() {
@Override
public void run() {
String money = "一萬遊戲幣";
System.out.println("我是買家,我帶着" + money + "過來了");
try {
Thread.sleep(4000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("買家到達地圖上交易地點");
try {
System.out.println("我是買家,換回了" + exchanger.exchange(money));
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}).start();
}
複製代碼
我是賣家,我帶着裝備過來了!
我是買家,我帶着一萬遊戲幣過來了
賣家達到交易地點
買家到達交易地點
我是買家,換回了裝備
我是賣家,換回了一萬遊戲幣
複製代碼