儘管線程是比較的神祕且具有一定的不可控性,但我們依然可以儘可能地對其進行管理和“控制”
運用CountDownLatch、CyclicBarrier、Semaphore等 在很大程度上可以幫我們對線程的一些執行順序等進行管理
線程等待的另一種形式:
java線程中除了使用 Object的wait()與notify()等來進行等待與喚醒外,CountDownLatch在一定程度上能夠更方便地進行一個或多個線程的等待操作。
適用背景如,將一個任務裁剪分發給多個工作者進行執行,然後再從多個工作者處匯聚得出總結果。
[ join()方法也能適用上面的例子,join的介紹可見博主的“java線程等待/通知機制及中斷”文章,join()可理解爲將上面提到的裁剪的零散任務按串行執行等待,而CountDownLatch是並行處理遇到wait後等待,直到countDown爲0,才放行]
下面開始介紹CountDownLatch
1、CountDownLatch(int count) 構造接受一個int型的參數作爲計數器,每當調用countDown()方法後會使count-1,在CountDownLatch以await()方法阻塞時,,若檢查到count達到0,則從其await()返回,運行繼續執行之後的步驟操作。CountDownLatch的countDown()方法沒有限制使用域(即在任何地方均可調用,若需要在多個線程中調用,只要將本類對象的引用傳遞進線程即可)注意,CountDownLatch的計數器在初始化時傳遞進去後就無法更改或重置
2、public void await() throws InterruptedException
使當前線程在鎖存器計數值至零之前一直等待,除非線程被中斷。
如果當前計數爲零,則此方法立即返回。
如果當前計數大於零,則出於線程調度目的,將禁用當前線程,且在發生以下兩種情況之一前,該線程將一直處於休眠狀態:
①由於調用 countDown() 方法,計數到達零;②其他某個線程中斷當前線程。
如果當前線程: 在進入此方法時已經設置了該線程的中斷狀態;或者在等待時被中斷,
則拋出 InterruptedException,並且清除當前線程的已中斷狀態。
另外,可以使用await(long timeout,TimeUnit unit) 設置等待超時,防止一直阻塞
3、①測試例子:
<span style="font-size:14px;">import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
public class CountdownlatchTest {
public static void fun1() throws InterruptedException{
CountDownLatch aa=new CountDownLatch(2);
System.out.println("0");
aa.countDown();
long time1=System.currentTimeMillis();
aa.await(5, TimeUnit.SECONDS);
long time2=System.currentTimeMillis()-time1;
System.out.println("1");
System.out.println("-----"+time2+"毫秒-----");
}
public static void fun2() throws InterruptedException{
CountDownLatch aa=new CountDownLatch(1);
System.out.println("2");
aa.countDown();
aa.await();
System.out.println("3");
aa.await();
System.out.println("4");
System.out.println("-----------");
}
public static void main(String[] args) throws InterruptedException {
/*fun2()由於設置了計數器爲1,
* countDown之後,
* 即便多次await()也不會再阻塞*/
fun2();
/*fun1()由於只調用了一次countDown(),
* 會一直阻塞到5秒超時,
* 然後從await()返回再繼續執行*/
fun1();
}
}</span>
運行結果:
<span style="font-size:14px;">2
3
4
-----------
0
1
-----5001毫秒-----</span>
②用法:
第一個對象實例是一個啓動信號,在 driver 爲繼續執行 worker 做好準備之前,它會阻止所有的 worker 繼續執行。
第二個對象實例是一個完成信號,它允許 driver 在完成所有 worker 之前一直等待。
class Driver { // ...
void main() throws InterruptedException {
CountDownLatch startSignal = new CountDownLatch(1);
CountDownLatch doneSignal = new CountDownLatch(N);
for (int i = 0; i < N; ++i) // create and start threads
new Thread(new Worker(startSignal, doneSignal)).start();
doSomethingElse(); // don't let run yet
startSignal.countDown(); // let all threads proceed
doSomethingElse();
doneSignal.await(); // wait for all to finish
}
}
class Worker implements Runnable {
private final CountDownLatch startSignal;
private final CountDownLatch doneSignal;
Worker(CountDownLatch startSignal, CountDownLatch doneSignal) {
this.startSignal = startSignal;
this.doneSignal = doneSignal;
}
public void run() {
try {
startSignal.await();
doWork();
doneSignal.countDown();
} catch (InterruptedException ex) {} // return;
}
void doWork() { ... }
}
下面開始介紹CyclicBarrier
它允許一組線程互相等待,直到到達某個公共屏障點 (common barrier point)。在涉及一組固定大小的線程的程序中,這些線程必須不時地互相等待,此時 CyclicBarrier 很有用。因爲該 barrier 在釋放等待線程後可以重用
CyclicBarrier(int parties) 創建一個新的 CyclicBarrier,它將在給定數量的參與者(線程)都執行等待狀態時啓動(即所有線程都調用await達到屏障)。 |
CyclicBarrier(int parties,Runnable barrierAction) 創建一個新的 CyclicBarrier,它將在給定數量的參與者(線程)都執行等待狀態時啓動,並在解除等待前先執行給定的屏障操作(barrierAction的run()) |
驗證:
import java.util.concurrent.CyclicBarrier;
public class CyclicBarrierTest2 {
static CyclicBarrier c = new CyclicBarrier(2, new A());
public static void main(String[] args) {
new Thread(new Runnable() {
@Override
public void run() {
try {
c.await();
} catch (Exception e) {
}
System.out.println(1);
}
}).start();
try {
c.await();
} catch (Exception e) {
}
System.out.println(2);
}
static class A implements Runnable {
@Override
public void run() {
try {
Thread.sleep(2000);//睡眠2秒,驗證是否A優先執行
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(3);
}
}
}
運行結果:3 1 2
或 3 2 1 (即便使A睡眠再久 也是A結束後再從屏障返回)Semaphore:
Semaphore用於控制同時訪問特定資源的線程數量。比如在頻繁地操作時可以有千百個線程進行執行操作,但其中可能涉及到對數據庫的後續存入操作等,在數據庫線程池可能只提供了部分連接數。則可以使用Semaphore進行控制。
例:
<span style="font-size:14px;">import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Semaphore;
public class SemaphoreTest {
private static final int THREAD_COUNT = 30;
private static ExecutorService threadPool = Executors.newFixedThreadPool(THREAD_COUNT);
private static Semaphore s = new Semaphore(10);
public static void main(String[] args) {
for (int i = 0; i < THREAD_COUNT; i++) {
threadPool.execute(new Runnable() {
@Override
public void run() {
try {
s.acquire();
System.out.println("save data");
s.release();//若去掉這行,則沒有進行歸還許可,導致只輸出10行數據
} catch (InterruptedException e) {
}
}
});
}
threadPool.shutdown();
}
}</span>
分析:30個線程進行執行操作,但Semaphore只允許了10個併發量,通過acquire()方法從其中獲取通行證,在使用完畢後,調用release()歸還,這樣第11個線程纔有機會從隊列中再獲取許可進行後續操作。
使用示例如 線程池A進行任務處理後遇到需要執行數據庫操作時使用Semaphore控制併發(數量與數據庫線程池B的連接數對應)如:
Thread A.run(){
.....
...
Job aJob=new Job(){....}
Semaphore.acquire()
Feture F=DataThreadPool.submit(aJob)
if(F.get())
Semaphore.release()
....
..
}
暫時先談這麼多咯。。
CountDownLatch 與 CyclicBarrier 簡要區別:(再囉嗦幾句。。)
CountDownLatch(int N) N表示計數器值,調用countDown()方法來使N-1,其wait()方法會阻塞當前線程,直到N=0
CyclicBarrier (int parties) parties爲屏障攔截的線程數 當有parties個線程都調用了CyclicBarrier的await()以後,才均可放行,否則等待。
[CountDownLatch 只需在任何地方調用N次countDown() 而CyclicBarrier要parties個線程調用它的await()]