Java並發編程實踐 目錄
並發編程 04—— 閉鎖CountDownLatch 與 柵欄CyclicBarrier
並發編程 06—— CompletionService : Executor 和 BlockingQueue
並發編程 10—— 任務取消 之 關閉 ExecutorService
並發編程 12—— 任務取消與關閉 之 shutdownNow 的局限性
並發編程 13—— 線程池的使用 之 配置ThreadPoolExecutor 和 飽和策略
概述
第1部分 閉鎖
閉鎖是一種同步工具類,可以延遲線程的進度直到其到達終止狀態。閉鎖的作用相當於一扇門:在閉鎖到達結束狀態之前,這扇門一直是關閉的,並且沒有任何線程能通過,當到達結束狀態時,這扇門會打開並允許所有的線程通過。當閉鎖到達結束狀態后,將不會再改變狀態,因此這扇門將永遠保持打開狀態。閉鎖可以用來確保某些活動直到其他活動都完成后才繼續執行。例如:
- 確保某個計算在其需要的所有資源都被初始化之后才繼續執行。
- 確保某個服務在其依賴的所有其他服務都已經啟動之后才啟動。
- 等待直到某個操作的所有參與者都就緒再繼續執行。
CountDownLatch是一種靈活的閉鎖實現,可以再上述各種情況中使用,它可以使一個或多個線程等待一組事件發生。閉鎖狀態包括一個計數器,該計數器被初始化為一個正數,表示需要等待的事件數量。countDown方法遞減計數器,表示有一個事件已經發生了,而await方法等待計數器達到0,這表示所有需要等待的事件都已經發生。如果計數器的值非零,那么await會一直阻塞直到計數器為零,或者等待中的線程中斷,或者等待超時。
下面程序TestHarness中給出了閉鎖的兩種常見用法。TestHarness創建一定數量的線程,利用它們並發地執行指定的任務。它使用兩個閉鎖,分別表示“起始門”和“結束門”。起始門計數器的初始值為1,而結束門計數器的初始值為工作線程的數量。每個工作線程首先要做到就是在啟動門上等待,從而確保所有線程都就緒后才開始執行。而每個線程要做的最后一個事情是將調用結束門的countDown方法減1 ,這能使主線程高效低等待直到所有工作線程都執行完畢,因此可以統計所消耗的時間。
1 package com.concurrency.BasicBuildingBlocks_5; 2 3 import java.util.concurrent.CountDownLatch; 4 5 /** 6 * 5.11 在計時測試中使用CountDownLatch來啟動和停止線程(閉鎖) 7 * 8 * @ClassName: TestHarness 9 * TODO 10 * @author Xingle 11 * @date 2014-9-4 下午2:56:29 12 */ 13 public class TestHarness { 14 15 int nThreads ; 16 Runnable task; 17 public TestHarness(int nThreads,Runnable task){ 18 this.nThreads = nThreads; 19 this.task = task; 20 } 21 public long timeTask(){ 22 23 //起始門 24 final CountDownLatch startGate = new CountDownLatch(1); 25 //結束門 26 final CountDownLatch endGate = new CountDownLatch(nThreads); 27 for(int i = 0;i<nThreads;i++){ 28 Thread thread = new Thread(){ 29 public void run(){ 30 //每個線程在啟動門上等待,確保所有線程都就緒后才開始 31 try { 32 startGate.await();//等待計數器達到0 33 try{ 34 task.run(); 35 }finally{ 36 //每個線程結束后,調用countDown遞減計數器,表示一個事件發生 37 endGate.countDown(); 38 } 39 } catch (InterruptedException e) { 40 e.printStackTrace(); 41 } 42 43 } 44 }; 45 thread.start(); 46 } 47 long start = System.nanoTime(); 48 //啟動門發生 49 startGate.countDown(); 50 try { 51 //等待結束門的線程都結束 52 endGate.await(); 53 } catch (InterruptedException e) { 54 e.printStackTrace(); 55 } 56 long end = System.nanoTime(); 57 return end - start; 58 } 59 }
測試程序:
1 package com.concurrency.BasicBuildingBlocks_5; 2 3 /** 4 * 5 * @ClassName: TestHarnessMain 6 * TODO 7 * @author Xingle 8 * @date 2014-9-4 下午3:27:18 9 */ 10 public class TestHarnessMain { 11 12 public static void main(String[] args){ 13 Runnable task = new Runnable() { 14 15 @Override 16 public void run() { 17 System.out.println("執行任務,我是線程:"+Thread.currentThread().getName()); 18 } 19 }; 20 int count = 10; 21 TestHarness testHarness = new TestHarness(count, task ); 22 long time = testHarness.timeTask(); 23 System.out.println("閉鎖 測試結果 執行"+count+"個線程"+" 一共用時:"+time); 24 } 25 }
執行結果:
第2部分 柵欄
上面已經看到通過閉鎖來啟動一組相關的操作,或者等待一組相關的操作結束。閉鎖是一次性對象,一旦進入終止狀態,就不能被重置。
柵欄(Barrier)類似於閉鎖,它能阻塞一組線程直到某個事件發生。柵欄與閉鎖的關鍵區別在於,所有線程必須同時到達柵欄位置,才能繼續執行。閉鎖用於等待事件,而柵欄用於等待其他線程。(柵欄則是所有線程相互等待,直到所有線程都到達某一點時才打開柵欄,然后線程可以繼續執行。)
CyclicBarrier 可以使一定數量的參與方反復地在柵欄位置匯集,它在並行迭代算法中非常有用。CyclicBarrier支持一個可選的 Runnable
參數,當線程通過柵欄時,runnable對象將被調用。構造函數CyclicBarrier(int parties, Runnable barrierAction)
,當線程在CyclicBarrier對象上調用await()
方法時,柵欄的計數器將增加1,當計數器為parties
時,柵欄將打開。
1 package com.concurrency.BasicBuildingBlocks_5; 2 3 import java.util.concurrent.BrokenBarrierException; 4 import java.util.concurrent.CyclicBarrier; 5 6 /** 7 * 8 * @ClassName: Worker 9 * @author Xingle 10 * @date 2014-9-9 上午10:41:17 11 */ 12 public class Worker implements Runnable { 13 14 final int id; 15 final CyclicBarrier barrier; 16 17 public Worker(int id, CyclicBarrier barrier) { 18 this.id = id; 19 this.barrier = barrier; 20 } 21 22 @Override 23 public void run() { 24 System.out.println(this.id + " start to run!"); 25 try { 26 this.barrier.await(); 27 } catch (InterruptedException e) { 28 e.printStackTrace(); 29 } catch (BrokenBarrierException e) { 30 e.printStackTrace(); 31 } 32 33 } 34 35 }
測試程序:
1 package com.concurrency.BasicBuildingBlocks_5; 2 3 import java.util.concurrent.CyclicBarrier; 4 5 /** 6 * 7 * @ClassName: Beer 8 * 有五個人參與跑步,規定五個人只要都跑到終點了,大家可以喝啤酒。但是,只要有一個人沒到終點,就不能喝。 這里沒有要求大家要同時起跑 9 * @author Xingle 10 * @date 2014-9-9 上午10:40:36 11 */ 12 public class Beer { 13 public static void main(String[] args){ 14 final int count = 5; 15 final CyclicBarrier barrier = new CyclicBarrier(count, new Runnable() { 16 17 @Override 18 public void run() { 19 System.out.println("drink beer!"); 20 } 21 }); 22 23 for (int i =0; i<count;i++){ 24 new Thread(new Worker(i, barrier)).start(); 25 } 26 } 27 28 }
執行結果:
1.《並發編程實戰》 5.5 同步工具類
2.盡量把CyclicBarrier和CountDownLatch的區別說通俗點