一個長頭髮、穿着清爽的小姐姐,拿着一個嶄新的Mac筆記本向我走來,看着來勢洶洶,我心想着確定是技術大佬吧!可是我也是一個才華橫溢的人,穩住咱們能贏。
java
面試官:看你簡歷上有寫熟悉併發編程,CountDownLatch必定用過吧,跟我說說它!面試
我:CountDownLatch是JDK提供的一個同步工具,它可讓一個或多個線程等待,一直等到其餘線程中執行完成一組操做。編程
面試官:CountDownLatch有哪些經常使用的方法?微信
我:有countDown
方法和await
方法,CountDownLatch在初始化時,須要指定用給定一個整數做爲計數器。當調用countDown
方法時,計數器會被減1;當調用await
方法時,若是計數器大於0時,線程會被阻塞,一直到計數器被countDown
方法減到0時,線程纔會繼續執行。計數器是沒法重置的,當計數器被減到0時,調用await
方法都會直接返回。併發
面試官:調用
countDown
方法時,線程也會阻塞嘛?dom
我:不會的,調用countDown
的線程能夠繼續執行,不須要等待計數器被減到0,只是調用await方法的線程須要等待。ide
歡迎關注微信公衆號:萬貓學社,每週一分享Java技術乾貨。工具
面試官:能夠舉一個使用CountDownLatch的例子嗎?測試
我:好比張3、李四和王五幾我的約好去飯店一塊兒去吃飯,這幾我的都是比較紳士,要等到全部人都到齊之後才讓服務員上菜。這種場景就能夠用到CountDownLatch。ui
面試官:能夠寫一下嗎?
我:固然能夠,這是張3、李四和王五的顧客類:
package onemore.study; import java.text.SimpleDateFormat; import java.util.Date; import java.util.Random; import java.util.concurrent.CountDownLatch; public class Customer implements Runnable { private CountDownLatch latch; private String name; public Customer(CountDownLatch latch, String name) { this.latch = latch; this.name = name; } @Override public void run() { try { SimpleDateFormat sdf = new SimpleDateFormat("HH:mm:ss.SSS"); Random random = new Random(); System.out.println(sdf.format(new Date()) + " " + name + "出發去飯店"); Thread.sleep((long) (random.nextDouble() * 3000) + 1000); System.out.println(sdf.format(new Date()) + " " + name + "到了飯店"); latch.countDown(); } catch (Exception e) { e.printStackTrace(); } } }
這是服務員類:
package onemore.study; import java.text.SimpleDateFormat; import java.util.Date; import java.util.concurrent.CountDownLatch; public class Waitress implements Runnable { private CountDownLatch latch; private String name; public Waitress(CountDownLatch latch, String name) { this.latch = latch; this.name = name; } @Override public void run() { try { SimpleDateFormat sdf = new SimpleDateFormat("HH:mm:ss.SSS"); System.out.println(sdf.format(new Date()) + " " + name + "等待顧客"); latch.await(); System.out.println(sdf.format(new Date()) + " " + name + "開始上菜"); } catch (Exception e) { e.printStackTrace(); } } }
而後,再寫一個測試類,用於模擬上面所說的場景:
package onemore.study; import java.util.ArrayList; import java.util.List; import java.util.concurrent.CountDownLatch; public class CountDownLatchTester { public static void main(String[] args) throws InterruptedException { CountDownLatch latch = new CountDownLatch(3); List<Thread> threads = new ArrayList<>(3); threads.add(new Thread(new Customer(latch, "張三"))); threads.add(new Thread(new Customer(latch, "李四"))); threads.add(new Thread(new Customer(latch, "王五"))); for (Thread thread : threads) { thread.start(); } Thread.sleep(100); new Thread(new Waitress(latch, "♥小芳♥")).start(); } }
運行之後的結果應該是這樣的:
15:25:53.015 王五出發去飯店 15:25:53.015 李四出發去飯店 15:25:53.015 張三出發去飯店 15:25:53.062 ♥小芳♥等待顧客 15:25:54.341 張三到了飯店 15:25:54.358 李四到了飯店 15:25:56.784 王五到了飯店 15:25:56.784 ♥小芳♥開始上菜
能夠看到,服務員小芳在調用await
方法時一直阻塞着,一直等到三個顧客都調用了countDown
方法才繼續執行。
歡迎關注微信公衆號:萬貓學社,每週一分享Java技術乾貨。
面試官:若是有一個顧客遲遲沒到,飯店都打樣了,也不能一直等啊,應該這麼辦?
我:可使用await
方法的另外一個重載,傳入等待的超時時間,好比服務員只等3秒鐘,能夠把服務員類中的
latch.await();
改爲:
latch.await(3, TimeUnit.SECONDS);
運行結果多是這樣的:
17:24:40.915 張三出發去飯店 17:24:40.915 李四出發去飯店 17:24:40.915 王五出發去飯店 17:24:40.948 ♥小芳♥等待顧客 17:24:43.376 李四到了飯店 17:24:43.544 王五到了飯店 17:24:43.951 ♥小芳♥開始上菜 17:24:44.762 張三到了飯店
能夠看到,服務員小芳在調用await
方法時雖然被阻塞了,可是時間超過3秒後,沒等顧客張三調用countDown
方法就繼續執行開始上菜了。
歡迎關注微信公衆號:萬貓學社,每週一分享Java技術乾貨。
面試官:CountDownLatch的實現原理是什麼?
我:CountDownLatch有一個內部類叫作Sync,它繼承了AbstractQueuedSynchronizer類,其中維護了一個整數state
,而且保證了修改state
的可見性和原子性。
建立CountDownLatch實例時,也會建立一個Sync的實例,同時把計數器的值傳給Sync實例,具體是這樣的:
public CountDownLatch(int count) { if (count < 0) throw new IllegalArgumentException("count < 0"); this.sync = new Sync(count); }
在 countDown
方法中,只調用了Sync實例的releaseShared
方法,具體是這樣的:
public void countDown() { sync.releaseShared(1); }
其中的releaseShared
方法,先對計數器進行減1操做,若是減1後的計數器爲0,喚醒被await方法阻塞的全部線程,具體是這樣的:
public final boolean releaseShared(int arg) { if (tryReleaseShared(arg)) { //對計數器進行減一操做 doReleaseShared();//若是計數器爲0,喚醒被await方法阻塞的全部線程 return true; } return false; }
其中的tryReleaseShared
方法,先獲取當前計數器的值,若是計數器爲0時,就直接返回;若是不爲0時,使用CAS方法對計數器進行減1操做,具體是這樣的:
protected boolean tryReleaseShared(int releases) { for (;;) {//死循環,若是CAS操做失敗就會不斷繼續嘗試。 int c = getState();//獲取當前計數器的值。 if (c == 0)// 計數器爲0時,就直接返回。 return false; int nextc = c-1; if (compareAndSetState(c, nextc))// 使用CAS方法對計數器進行減1操做 return nextc == 0;//若是操做成功,返回計數器是否爲0 } }
在await
方法中,只調用了Sync實例的acquireSharedInterruptibly
方法,具體是這樣的:
public void await() throws InterruptedException { sync.acquireSharedInterruptibly(1); }
其中acquireSharedInterruptibly
方法,判斷計數器是否爲0,若是不爲0則阻塞當前線程,具體是這樣的:
public final void acquireSharedInterruptibly(int arg) throws InterruptedException { if (Thread.interrupted()) throw new InterruptedException(); if (tryAcquireShared(arg) < 0)//判斷計數器是否爲0 doAcquireSharedInterruptibly(arg);//若是不爲0則阻塞當前線程 }
其中tryAcquireShared
方法,是AbstractQueuedSynchronizer中的一個模板方法,其具體實如今Sync類中,其主要是判斷計數器是否爲零,若是爲零則返回1,若是不爲零則返回-1,具體是這樣的:
protected int tryAcquireShared(int acquires) { return (getState() == 0) ? 1 : -1; }
面試官:嗯,很不錯,立刻給你發offer。
微信公衆號:萬貓學社
微信掃描二維碼
得到更多Java技術乾貨