在 JUC 下包含了一些經常使用的同步工具類,今天就來詳細介紹一下,CountDownLatch,CyclicBarrier,Semaphore 的使用方法以及它們之間的區別。程序員
先看一下,CountDownLatch 源碼的官方介紹。dom
意思是,它是一個同步輔助器,容許一個或多個線程一直等待,直到一組在其餘線程執行的操做所有完成。ide
public CountDownLatch(int count) {
if (count < 0) throw new IllegalArgumentException("count < 0");
this.sync = new Sync(count);
}複製代碼
它的構造方法,會傳入一個 count 值,用於計數。函數
經常使用的方法有兩個:工具
public void await() throws InterruptedException {
sync.acquireSharedInterruptibly(1);
}
public void countDown() {
sync.releaseShared(1);
}複製代碼
當一個線程調用await方法時,就會阻塞當前線程。每當有線程調用一次 countDown 方法時,計數就會減 1。當 count 的值等於 0 的時候,被阻塞的線程纔會繼續運行。學習
如今設想一個場景,公司項目,線上出現了一個緊急 bug,被客戶投訴,領導焦急的過來,想找人迅速的解決這個 bug 。ui
那麼,一我的解決確定速度慢啊,因而叫來張三和李四,一塊兒分工解決。終於,當他們兩個都作完了本身所須要作的任務以後,領導才能夠答覆客戶,客戶也就消氣了(沒辦法啊,客戶是上帝嘛)。this
因而,咱們能夠設計一個 Worker 類來模擬單我的修復 bug 的過程,主線程就是領導,一直等待全部 Worker 任務執行結束,主線程才能夠繼續往下走。spa
public class CountDownTest {
static SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
public static void main(String[] args) throws InterruptedException {
CountDownLatch latch = new CountDownLatch(2);
Worker w1 = new Worker("張三", 2000, latch);
Worker w2 = new Worker("李四", 3000, latch);
w1.start();
w2.start();
long startTime = System.currentTimeMillis();
latch.await();
System.out.println("bug所有解決,領導能夠給客戶交差了,任務總耗時: "+ (System.currentTimeMillis() - startTime));
}
static class Worker extends Thread{
String name;
int workTime;
CountDownLatch latch;
public Worker(String name, int workTime, CountDownLatch latch) {
this.name = name;
this.workTime = workTime;
this.latch = latch;
}
@Override
public void run() {
System.out.println(name+"開始修復bug,當前時間:"+sdf.format(new Date()));
doWork();
System.out.println(name+"結束脩復bug,當前時間: "+sdf.format(new Date()));
latch.countDown();
}
private void doWork() {
try {
//模擬工做耗時
Thread.sleep(workTime);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}複製代碼
原本須要 5 秒完成的任務,兩我的 3 秒就完成了。我只能說,這程序員的工做效率真是太太過高了。線程
barrier 英文是屏障,障礙,柵欄的意思。cyclic是循環的意思,就是說,這個屏障能夠循環使用(什麼意思,等下我舉例子就知道了)。源碼官方解釋是:
A synchronization aid that allows a set of threads to all wait for each other to reach a common barrier point . The barrier is called cyclic because it can be re-used after the waiting threads are released.
一組線程會互相等待,直到全部線程都到達一個同步點。這個就很是有意思了,就像一羣人被困到了一個柵欄前面,只有等最後一我的到達以後,他們才能夠協力把柵欄(屏障)突破。
CyclicBarrier 提供了兩種構造方法:
public CyclicBarrier(int parties) {
this(parties, null);
}
public CyclicBarrier(int parties, Runnable barrierAction) {
if (parties <= 0) throw new IllegalArgumentException();
this.parties = parties;
this.count = parties;
this.barrierCommand = barrierAction;
}複製代碼
第一個構造的參數,指的是須要幾個線程一塊兒到達,纔可使全部線程取消等待。第二個構造,額外指定了一個參數,用於在全部線程達到屏障時,優先執行 barrierAction。
如今模擬一個經常使用的場景,一組運動員比賽 1000 米,只有在全部人都準備完成以後,才能夠一塊兒開跑(額,先忽略裁判吹口哨的細節)。
定義一個 Runner 類表明運動員,其內部維護一個共有的 CyclicBarrier,每一個人都有一個準備時間,準備完成以後,會調用 await 方法,等待其餘運動員。 當全部人準備都 OK 時,就能夠開跑了。
public class BarrierTest {
public static void main(String[] args) {
CyclicBarrier barrier = new CyclicBarrier(3); //①
Runner runner1 = new Runner(barrier, "張三");
Runner runner2 = new Runner(barrier, "李四");
Runner runner3 = new Runner(barrier, "王五");
ExecutorService service = Executors.newFixedThreadPool(3);
service.execute(runner1);
service.execute(runner2);
service.execute(runner3);
service.shutdown();
}
}
class Runner implements Runnable{
private CyclicBarrier barrier;
private String name;
public Runner(CyclicBarrier barrier, String name) {
this.barrier = barrier;
this.name = name;
}
@Override
public void run() {
try {
//模擬準備耗時
Thread.sleep(new Random().nextInt(5000));
System.out.println(name + ":準備OK");
barrier.await();
System.out.println(name +": 開跑");
} catch (InterruptedException e) {
e.printStackTrace();
} catch (BrokenBarrierException e){
e.printStackTrace();
}
}
}複製代碼
能夠看到,咱們已經實現了須要的功能。可是,有的同窗就較真了,說你這不行啊,哪有運動員都準備好以後就開跑的,你還把裁判放在眼裏嗎,裁判不吹口哨,你敢跑一個試試。
好吧,也確實是這樣一個理兒,那麼,咱們就實現一下,讓裁判吹完口哨以後,他們再一塊兒開跑吧。
這裏就要用到第二個構造函數了,因而我把代碼 ① 處稍微修改一下。
CyclicBarrier barrier = new CyclicBarrier(3, new Runnable() {
@Override
public void run() {
try {
System.out.println("等裁判吹口哨...");
//這裏停頓兩秒更便於觀察線程執行的前後順序
Thread.sleep(2000);
System.out.println("裁判吹口哨->>>>>");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});複製代碼
執行結果:
張三:準備OK
李四:準備OK
王五:準備OK
等裁判吹口哨...
裁判吹口哨->>>>>
張三: 開跑
李四: 開跑
王五: 開跑複製代碼
能夠看到,雖然三我的都已經準備 OK了,可是,只有裁判吹完口哨以後,他們才能夠開跑。
剛纔,提到了循環利用是怎麼體現的呢。 我如今把屏障值改成 2,而後增長一個「趙六」 一塊兒參與賽跑。被修改的部分以下:
此時觀察,打印結果:
張三:準備OK
李四:準備OK
等裁判吹口哨...
裁判吹口哨->>>>>
李四: 開跑
張三: 開跑
王五:準備OK
趙六:準備OK
等裁判吹口哨...
裁判吹口哨->>>>>
趙六: 開跑
王五: 開跑複製代碼
發現沒,能夠分兩批,第一批先跑兩我的,而後第二批再跑兩我的。也就是屏障的循環使用。
Semaphore 信號量,用來控制同一時間,資源可被訪問的線程數量,通常可用於流量的控制。
打個比方,如今有一段公路交通比較擁堵,那怎麼辦呢。此時,就須要警察叔叔出面,限制車的流量。
好比,如今有 20 輛車要經過這個地段, 警察叔叔規定同一時間,最多隻能經過 5 輛車,其餘車輛只能等待。只有拿到許可的車輛可經過,等車輛經過以後,再歸還許可,而後把它發給等待的車輛,得到許可的車輛再通行,依次類推。
public class SemaphoreTest {
private static int count = 20;
public static void main(String[] args) {
ExecutorService executorService = Executors.newFixedThreadPool(count);
//指定最多隻能有五個線程同時執行
Semaphore semaphore = new Semaphore(5);
Random random = new Random();
for (int i = 0; i < count; i++) {
final int no = i;
executorService.execute(new Runnable() {
@Override
public void run() {
try {
//得到許可
semaphore.acquire();
System.out.println(no +":號車可通行");
//模擬車輛通行耗時
Thread.sleep(random.nextInt(2000));
//釋放許可
semaphore.release();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
}
executorService.shutdown();
}
}複製代碼
打印結果我就不寫了,須要讀者自行觀察,就會發現,第一批是五個車同時通行。而後,後邊的車才能夠依次通行,可是同時通行的車輛不能超過 5 輛。
細心的讀者,就會發現,這許可一共就發 5 個,那等第一批車輛用完釋放以後, 第二批的時候應該發給誰呢?
這確實是一個問題。全部等待的車輛都想先拿到許可,先通行,怎麼辦。這就須要,用到鎖了。就全部人都去搶,誰先搶到,誰就先走唄。
咱們去看一下 Semaphore的構造函數,就會發現,能夠傳入一個 boolean 值的參數,控制搶鎖是不是公平的。
public Semaphore(int permits) {
sync = new NonfairSync(permits);
}
public Semaphore(int permits, boolean fair) {
sync = fair ? new FairSync(permits) : new NonfairSync(permits);
}複製代碼
默認是非公平,能夠傳入 true 來使用公平鎖。(鎖的機制是經過AQS,如今不細講,等我之後更新哦)
這裏簡單的說一下,什麼是公平非公平吧。
公平的話,就是你車來了,就按照先來後到的順序正常走就好了。不公平的,也許就是,某位司機大哥膀大腰圓,手戴名錶,脖子帶粗金項鍊。別人一看惹不起,我還躲不起嗎,都給這位大哥讓道。你就算車走在人家前面了,你也不敢跟人家搶啊。
最後,那就是他先拿到許可證通行了。剩下的人再去搶,說不定又來一個,是警察叔叔的私交好友。(行吧行吧,其餘司機只能一臉的生無可戀,怎麼過個馬路都這麼費勁啊。。。)
若是本文對你有用,歡迎點贊,評論,轉發。
學習是枯燥的,也是有趣的。我是「煙雨星空」,歡迎關注,可第一時間接收文章推送。