Java線程(CountDownLatch、CyclicBarrier、Semaphore)併發控制工具類

儘管線程是比較的神祕且具有一定的不可控性,但我們依然可以儘可能地對其進行管理和“控制”

運用CountDownLatch、CyclicBarrier、Semaphore等 在很大程度上可以幫我們對線程的一些執行順序等進行管理

線程等待的另一種形式:

java線程中除了使用 Object的wait()與notify()等來進行等待與喚醒外,CountDownLatch在一定程度上能夠更方便地進行一個或多個線程的等待操作。

適用背景如,將一個任務裁剪分發給多個工作者進行執行,然後再從多個工作者處匯聚得出總結果。

[ join()方法也能適用上面的例子,join的介紹可見博主的java線程等待/通知機制及中斷”文章,join()可理解爲將上面提到的裁剪的零散任務按串行執行等待,而CountDownLatch是並行處理遇到wait後等待,直到countDown爲0,才放行]

下面開始介紹CountDownLatch

1CountDownLatch(int count) 構造接受一個int型的參數作爲計數器,每當調用countDown()方法後會使count-1,在CountDownLatch以await()方法阻塞時,,若檢查到count達到0,則從其await()返回,運行繼續執行之後的步驟操作。CountDownLatch的countDown()方法沒有限制使用域(即在任何地方均可調用,若需要在多個線程中調用,只要將本類對象的引用傳遞進線程即可)注意,CountDownLatch的計數器在初始化時傳遞進去後就無法更改或重置

2public 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()]

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章