執行緒執行順序——CountDownLatch、CyclicBarrier 、join()、執行緒池
本文主要圍繞一個問題展開:執行緒執行順序,比如某個執行緒在其他執行緒併發執行完畢後最後執行,這裡分別用 CountDownLatch、CyclicBarrier 、join()、執行緒池來實現。
方法一——CyclicBarrier
CyclicBarrier 的字面意思是可迴圈(Cyclic)使用的屏障(Barrier),又叫同步屏障。它可以讓一組執行緒到達一個屏障(也可以叫同步點)時被阻塞,直到最後一個執行緒到達屏障時,屏障才會開門,所有被屏障攔截的執行緒才會繼續幹活。執行緒到達屏障的控制通過CyclicBarrier 的 await() 方法實現。
CyclicBarrier 的構造方法有 CyclicBarrier(int parties),其參數列示屏障攔截的執行緒數量,每個執行緒呼叫 await 方法告訴 CyclicBarrier 我已經到達了屏障,然後當前執行緒被阻塞。
CyclicBarrier 還提供一個建構函式 CyclicBarrier(int parties, Runnable barrierAction) ,用於線上程都到達屏障時,優先執行barrierAction 這個 Runnable 物件,然後都到達屏障的執行緒繼續執行。
實現原理
在 CyclicBarrier 的內部定義了一個 ReentrantLock 物件,每當一個執行緒呼叫 CyclicBarrier 的 await 方法時,將剩餘攔截的執行緒數減1,然後判斷剩餘攔截數是否為0,如果不是,進入 Lock 物件的條件佇列等待。如果是則執行 barrierAction 物件的 run 方法,然後將鎖的條件佇列中的所有執行緒放入鎖等待佇列中,這些執行緒會依次的獲取鎖、釋放鎖,接著先從 await 方法返回,再從 CyclicBarrier 的 await 方法中返回。
CyclicBarrier 和 CountDownLatch 比較
CountDownLatch 的作用是允許1或N個執行緒等待其他執行緒完成執行;而 CyclicBarrier 則是允許N個執行緒相互等待。
CountDownLatch 的計數器無法被重置;CyclicBarrier 的計數器可以被重置後使用,因此它被稱為是迴圈的 barrier。
示例
public class CyclicBarrierPractice {
static class Worker implements Runnable{
private String name;
private CyclicBarrier cyclicBarrier;
public Worker(String name, CyclicBarrier cyclicBarrier){
this.name = name;
this.cyclicBarrier = cyclicBarrier;
}
public void run(){
System.out.println(name + " is working");
try {
Thread.sleep(2000);
//到達屏障出(同步點)
cyclicBarrier.await();
//執行緒都到了後繼續向下執行,也可以不要下面程式碼,什麼都不做了
System.out.println(name + " do other things");
} catch (InterruptedException e) {
e.printStackTrace();
} catch (BrokenBarrierException e) {
e.printStackTrace();
}
}
}
static class Boss implements Runnable{
private String name;
public Boss(String name){
this.name = name;
}
public void run(){
System.out.println(name + " checks work");
}
}
public static void main(String[] args){
//其他執行緒都達到屏障後,再執行 boss 執行緒
CyclicBarrier cyclicBarrier = new CyclicBarrier(3, new Boss("boss"));
for(int i=0; i<3; i++){
new Thread(new Worker("worker"+i, cyclicBarrier)).start();
}
}
}
執行結果
worker0 is working
worker1 is working
worker2 is working
boss checks work
worker2 do other things
worker0 do other things
worker1 do other things
方法二——join
join() 是 Thread 類的一個方法,join() 方法的作用是等待當前執行緒結束,也即讓“主執行緒”等待“子執行緒”結束之後才能繼續執行。t.join() 方法阻塞呼叫此方法的執行緒 (calling thread),直到執行緒 t完成,此執行緒再繼續(看起來和同步呼叫類似);通常用於在 main 主執行緒內,等待其它執行緒完成後再繼續執行 main 主執行緒。
join 實現
Join 方法實現是通過 wait(Object 提供的方法)。 看原始碼知會進入 while(isAlive()) 迴圈;即只要子執行緒是活的,主執行緒就不停的等待。
示例
用 join 方式實現問題如下,在程式碼中 main 執行緒被阻塞直到 thread1,thread2,thread3 執行完,主執行緒才會順序的執行thread4.
public class JoinPractice {
static class Worker implements Runnable {
private String name;
public Worker(String name){
this.name = name;
}
@Override
public void run(){
System.out.println(name + " is working");
}
}
static class Boss implements Runnable{
private String name;
public Boss(String name){
this.name = name;
}
@Override
public void run(){
System.out.println("boss checks work");
}
}
public static void main(String[] args){
Worker worker1 = new Worker("worker1");
Worker worker2 = new Worker("worker2");
Worker worker3 = new Worker("worker3");
Boss boss = new Boss("boss");
Thread thread1 = new Thread(worker1);
Thread thread2 = new Thread(worker2);
Thread thread3 = new Thread(worker3);
Thread thread4 = new Thread(boss);
thread1.start();
thread2.start();
thread3.start();
try {
thread1.join();
thread2.join();
thread3.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
thread4.start();
}
}
執行結果
worker2 is working
worker1 is working
worker3 is working
boss checks work
方法三——CountDownLatch
Java 的 util.concurrent 包裡面的 CountDownLatch 其實可以把它看作一個計數器(倒數計時鎖),只不過這個計數器的操作是原子操作,同時只能有一個執行緒去操作這個計數器,也就是同時只能有一個執行緒去減這個計數器裡面的值。
你可以向 CountDownLatch 物件設定一個初始的數字作為計數值,任何呼叫這個物件上的 await() 方法都會阻塞,直到這個計數器的計數值被其他的執行緒減為 0 為止。
使用場景
CountDownLatch 的一個非常典型的應用場景是:有一個任務想要往下執行,但必須要等到其他的任務執行完畢後才可以繼續往下執行。假如我們這個想要繼續往下執行的任務呼叫一個 CountDownLatch 物件的 await() 方法,其他的任務執行完自己的任務後呼叫同一個CountDownLatch 物件上的 countDown() 方法,這個呼叫 await() 方法的任務將一直阻塞等待,直到這個CountDownLatch物件的計數值減到 0 為止。
示例
舉個例子,有三個工人在為老闆幹活,這個老闆有一個習慣,就是當三個工人把一天的活都幹完了的時候,他就來檢查所有工人所幹的活。記住這個條件:三個工人先全部幹完活,老闆才檢查。所以在這裡用 Java 程式碼設計兩個類,Worker 代表工人,Boss 代表老闆,程式碼使用了內部類實現。
public class OrderThreadExecute {
class Worker implements Runnable {
private CountDownLatch downLatch;
private String name;
public Worker(CountDownLatch downLatch, String name) {
this.downLatch = downLatch;
this.name = name;
}
@Override
public void run() {
this.doWork();
try {
TimeUnit.SECONDS.sleep(new Random().nextInt(10));
} catch (InterruptedException ie) {
}
System.out.println(this.name + "活幹完了!");
this.downLatch.countDown();
}
private void doWork() {
System.out.println(this.name + "正在幹活...");
}
}
class Boss implements Runnable {
private CountDownLatch downLatch;
public Boss(CountDownLatch downLatch) {
this.downLatch = downLatch;
}
@Override
public void run() {
System.out.println("老闆正在等所有的工人幹完活......");
try {
this.downLatch.await();
} catch (InterruptedException e) {
}
System.out.println("工人活都幹完了,老闆開始檢查了!");
}
}
public static void main(String[] args) {
ExecutorService executor = Executors.newCachedThreadPool();
CountDownLatch latch = new CountDownLatch(3);
OrderThreadExecute orderThread = new OrderThreadExecute();
Worker w1 = orderThread.new Worker(latch, "張三");
Worker w2 = orderThread.new Worker(latch, "李四");
Worker w3 = orderThread.new Worker(latch, "王二");
Boss boss = orderThread.new Boss(latch);
executor.execute(boss);
executor.execute(w3);
executor.execute(w2);
executor.execute(w1);
executor.shutdown();
}
}
執行結果
老闆正在等所有的工人幹完活......
王二正在幹活...
李四正在幹活...
張三正在幹活...
李四活幹完了!
王二活幹完了!
張三活幹完了!
工人活都幹完了,老闆開始檢查了!
CountDownLatch 與 join 比較
呼叫thread.join() 方法必須等thread 執行完畢,當前執行緒才能繼續往下執行,而CountDownLatch通過計數器提供了更靈活的控制,只要檢測到計數器為0當前執行緒就可以往下執行而不用管相應的thread是否執行完畢。
具體比較見文章:http://blog.csdn.net/nyistzp/article/details/51444487
方法四——執行緒池
當執行緒池的執行緒全部執行完畢後再執行主執行緒,示例程式碼如下。
public class ExecuteOrderPractice {
public void orderPractice(){
ExecutorService executorService = Executors.newFixedThreadPool(3);
for(int i = 0; i < 5; i++){
executorService.execute(new Runnable() {
@Override
public void run() {
try{
Thread.sleep(1000);
System.out.println(Thread.currentThread().getName() + " do something");
}catch (InterruptedException e){
e.printStackTrace();
}
}
});
}
executorService.shutdown();
while(true){
if(executorService.isTerminated()){
System.out.println("Finally do something ");
break;
}
try {
Thread.sleep(200);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
public static void main(String[] args){
new ExecuteOrderPractice().orderPractice();
}
}
執行結果
pool-1-thread-1 do something
pool-1-thread-3 do something
pool-1-thread-2 do something
pool-1-thread-3 do something
pool-1-thread-1 do something
Finally do something
參考資料
java 多執行緒 CountDownLatch與join()方法區別
CountDownLatch使用例項
Java如何判斷執行緒池所有任務是否執行完畢
相關文章
- join、volatile、newSingleThreadLatch 實現執行緒順序執行thread執行緒
- 執行緒和執行緒池執行緒
- 執行緒 執行緒池 Task執行緒
- 多執行緒【執行緒池】執行緒
- Python執行緒專題10:queue、多執行緒按順序執行Python執行緒
- Java執行緒池二:執行緒池原理Java執行緒
- Java 多執行緒基礎(十一)執行緒優先順序和守護執行緒Java執行緒
- Java中如何保證執行緒順序執行Java執行緒
- 多優先順序執行緒池實踐執行緒
- 二. 執行緒管理之執行緒池執行緒
- Android多執行緒之執行緒池Android執行緒
- kuangshenshuo-多執行緒-執行緒池執行緒
- 多執行緒之手撕執行緒池執行緒
- java多執行緒9:執行緒池Java執行緒
- 【java】【多執行緒】睡眠/守護/加入/禮讓執行緒,執行緒優先順序(4)Java執行緒
- 執行緒、開啟執行緒的兩種方式、執行緒下的Join方法、守護執行緒執行緒
- 執行緒池執行緒
- java 多執行緒CountDownLatchJava執行緒CountDownLatch
- 執行緒(一)——執行緒,執行緒池,Task概念+程式碼實踐執行緒
- 利用訊號量實現執行緒順序執行執行緒
- Android程式框架:執行緒與執行緒池Android框架執行緒
- 執行緒池建立執行緒的過程執行緒
- 【Java】【多執行緒】執行緒池簡述Java執行緒
- Java執行緒池一:執行緒基礎Java執行緒
- Java多執行緒-執行緒池的使用Java執行緒
- 執行緒池以及四種常見執行緒池執行緒
- java執行緒池趣味事:這不是執行緒池Java執行緒
- 執行緒與執行緒池的那些事之執行緒池篇(萬字長文)執行緒
- Java 併發:執行緒、執行緒池和執行器全面教程Java執行緒
- Java中命名執行器服務執行緒和執行緒池Java執行緒
- java--執行緒池--建立執行緒池的幾種方式與執行緒池操作詳解Java執行緒
- 【高併發】深入理解執行緒的執行順序執行緒
- 多執行緒系列(三):執行緒池基礎執行緒
- 執行緒池管理(1)-為什麼需要執行緒池執行緒
- 多執行緒------執行緒與程式/執行緒排程/建立執行緒執行緒
- java多執行緒系列:CountDownLatchJava執行緒CountDownLatch
- kafka多執行緒順序消費Kafka執行緒
- Android執行緒池Android執行緒
- java 執行緒池Java執行緒