java併發編程中CountDownLatch和CyclicBarrier的使用
在多線程程序設計中,經常會遇到一個線程等待一個或多個線程的場景,遇到這樣的場景應該如何解決?
如果是一個線程等待一個線程,則可以通過await()和notify()來實現;
如果是一個線程等待多個線程,則就可以使用CountDownLatch和CyclicBarrier來實現比較好的控制。
下面來詳細描述下CountDownLatch的應用場景:
例如:百米賽跑:8名運動員同時起跑,由於速度的快慢,肯定有會出現先到終點和晚到終點的情況,而終點有個統計成績的儀器,當所有選手到達終點時,它會統計所有人的成績並進行排序,然後把結果發送到彙報成績的系統。
其實這就是一個CountDownLatch的應用場景:一個線程或多個線程等待其他線程運行達到某一目標後進行自己的下一步工作,而被等待的“其他線程”達到這個目標後繼續自己下面的任務。
這個場景中:
1. 被等待的“其他線程”------>8名運動員
2. 等待“其他線程”的這個線程------>終點統計成績的儀器
那麼,如何來通過CountDownLatch來實現上述場景的線程控制和調度呢?
jdk中CountDownLatch類有一個常用的構造方法:CountDownLatch(int count);
兩個常用的方法:await()和countdown()
其 中count是一個計數器中的初始化數字,比如初始化的數字是2,當一個線程裏調用了countdown(),則這個計數器就減一,當線程調用了 await(),則這個線程就等待這個計數器變爲0,當這個計數器變爲0時,這個線程繼續自己下面的工作。下面是上述CountDownLatch場景的 實現:
Work類(運動員):
import java.util.concurrent.CountDownLatch;
public class Work implements Runnable {
private int id;
private CountDownLatch beginSignal;
private CountDownLatch endSignal;
public Work(int id, CountDownLatch begin, CountDownLatch end) {
this.id = id;
this.beginSignal = begin;
this.endSignal = end;
}
@Override
public void run() {
try {
beginSignal.await();
System.out.println("起跑...");
System.out.println("work" + id + "到達終點");
endSignal.countDown();
System.out.println("work" + id + "繼續幹其他事情");
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
Main類(終點統計儀器):
import java.util.concurrent.CountDownLatch;
public class Main {
public static void main(String[] args) {
CountDownLatch begSignal = new CountDownLatch(1);
CountDownLatch endSignal = new CountDownLatch(8);
for (int i = 0; i < 8; i++) {
new Thread(new Work(i, begSignal, endSignal)).start();
}
try {
begSignal.countDown(); //統一起跑
endSignal.await(); //等待運動員到達終點
System.out.println("結果發送到彙報成績的系統");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
下面詳細描述下CyclicBarrier的應用場景:
有四個遊戲玩家玩遊戲,遊戲有三個關卡,每個關卡必須要所有玩家都到達後才能允許通關。
其 實這個場景裏的玩家中如果有玩家A先到了關卡1,他必須等待其他所有玩家都到達關卡1時才能通過,也就是說線程之間需要互相等待,這和 CountDownLatch的應用場景有區別,CountDownLatch裏的線程是到了運行的目標後繼續幹自己的其他事情,而這裏的線程需要等待其 他線程後才能繼續完成下面的工作。
jdk中CyclicBarrier類有兩個常用的構造方法:
1. CyclicBarrier(int parties)
這裏的parties也是一個計數器,例如,初始化時parties裏的計數是3,於是擁有該CyclicBarrier對象的線程當parties的計數爲3時就喚醒,注:這裏parties裏的計數在運行時當調用CyclicBarrier:await()時,計數就加1,一直加到初始的值
2. CyclicBarrier(int parties, Runnable barrierAction)
這裏的parties與上一個構造方法的解釋是一樣的,這裏需要解釋的是第二個入參(Runnable barrierAction),這個參數是一個實現Runnable接口的類的對象,也就是說當parties加到初始值時就出發barrierAction的內容。
下面來實現上述的應用場景:
Player類(玩家類)
GameBarrier類(關卡類,這裏控制玩家必須全部到達第一關結束的關口才能進入第二關)
import java.util.concurrent.CyclicBarrier;
public class GameBarrier {
public static void main(String[] args) {
CyclicBarrier cyclicBarrier = new CyclicBarrier(4, new Runnable() {
@Override
public void run() {
System.out.println("所有玩家進入第二關!");
}
});
for (int i = 0; i < 4; i++) {
new Thread(new Player(i, cyclicBarrier)).start();
}
}
}