一個長頭發(fā)、穿著清爽的小姐姐,拿著一個嶄新的Mac筆記本向我走來,看著來勢洶洶,我心想著肯定是技術大佬吧!但是我也是一個才華橫溢的人,穩(wěn)住我們能贏。
面試官:看你簡歷上有寫熟悉并發(fā)編程,CountDownLatch一定用過吧,跟我說說它!
我:CountDownLatch是JDK提供的一個同步工具,它可以讓一個或多個線程等待,一直等到其他線程中執(zhí)行完成一組操作。
面試官:CountDownLatch有哪些常用的方法?
我:有countDown方法和await方法,CountDownLatch在初始化時,需要指定用給定一個整數(shù)作為計數(shù)器。當調(diào)用countDown
方法時,計數(shù)器會被減1;當調(diào)用await方法時,如果計數(shù)器大于0時,線程會被阻塞,一直到計數(shù)器被countDown
方法減到0時,線程才會繼續(xù)執(zhí)行。計數(shù)器是無法重置的,當計數(shù)器被減到0時,調(diào)用await方法都會直接返回。
面試官:調(diào)用countDown方法時,線程也會阻塞嘛?
我:不會的,調(diào)用countDown的線程可以繼續(xù)執(zhí)行,不需要等待計數(shù)器被減到0,只是調(diào)用await方法的線程需要等待。
歡迎關注微信公眾號:萬貓學社,每周一分享Java技術干貨。
面試官:可以舉一個使用CountDownLatch的例子嗎?
我
:比如張三、李四和王五幾個人約好去飯店一起去吃飯,這幾個人都是比較紳士,要等到所有人都到齊以后才讓服務員上菜。這種場景就可以用到CountDownLatch。
面試官:可以寫一下嗎?
我:當然可以,這是張三、李四和王五的顧客類:
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 + "出發(fā)去飯店");
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(); } }
運行以后的結(jié)果應該是這樣的:
15:25:53.015 王五出發(fā)去飯店 15:25:53.015 李四出發(fā)去飯店 15:25:53.015 張三出發(fā)去飯店 15:25:53.062
?小芳?等待顧客 15:25:54.341 張三到了飯店 15:25:54.358 李四到了飯店 15:25:56.784 王五到了飯店
15:25:56.784 ?小芳?開始上菜
可以看到,服務員小芳在調(diào)用await方法時一直阻塞著,一直等到三個顧客都調(diào)用了countDown方法才繼續(xù)執(zhí)行。
歡迎關注微信公眾號:萬貓學社,每周一分享Java技術干貨。
面試官:如果有一個顧客遲遲沒到,飯店都打樣了,也不能一直等啊,應該這么辦?
我:可以使用await方法的另一個重載,傳入等待的超時時間,比如服務員只等3秒鐘,可以把服務員類中的
latch.await();
改成:
latch.await(3, TimeUnit.SECONDS);
運行結(jié)果可能是這樣的:
17:24:40.915 張三出發(fā)去飯店 17:24:40.915 李四出發(fā)去飯店 17:24:40.915 王五出發(fā)去飯店 17:24:40.948
?小芳?等待顧客 17:24:43.376 李四到了飯店 17:24:43.544 王五到了飯店 17:24:43.951 ?小芳?開始上菜
17:24:44.762 張三到了飯店
可以看到,服務員小芳在調(diào)用await方法時雖然被阻塞了,但是時間超過3秒后,沒等顧客張三調(diào)用countDown方法就繼續(xù)執(zhí)行開始上菜了。
歡迎關注微信公眾號:萬貓學社,每周一分享Java技術干貨。
面試官:CountDownLatch的實現(xiàn)原理是什么?
我:CountDownLatch有一個內(nèi)部類叫做Sync,它繼承了AbstractQueuedSynchronizer類,其中維護了一個整數(shù)state
,并且保證了修改state的可見性和原子性。
創(chuàng)建CountDownLatch實例時,也會創(chuàng)建一個Sync的實例,同時把計數(shù)器的值傳給Sync實例,具體是這樣的:
public CountDownLatch(int count) { if (count < 0) throw new
IllegalArgumentException("count < 0"); this.sync = new Sync(count); }
在 countDown方法中,只調(diào)用了Sync實例的releaseShared方法,具體是這樣的:
public void countDown() { sync.releaseShared(1); }
其中的releaseShared方法,先對計數(shù)器進行減1操作,如果減1后的計數(shù)器為0,喚醒被await方法阻塞的所有線程,具體是這樣的:
public final boolean releaseShared(int arg) { if (tryReleaseShared(arg)) {
//對計數(shù)器進行減一操作 doReleaseShared();//如果計數(shù)器為0,喚醒被await方法阻塞的所有線程 return true; }
return false; }
其中的tryReleaseShared
方法,先獲取當前計數(shù)器的值,如果計數(shù)器為0時,就直接返回;如果不為0時,使用CAS方法對計數(shù)器進行減1操作,具體是這樣的:
protected boolean tryReleaseShared(int releases) { for (;;)
{//死循環(huán),如果CAS操作失敗就會不斷繼續(xù)嘗試。 int c = getState();//獲取當前計數(shù)器的值。 if (c == 0)//
計數(shù)器為0時,就直接返回。 return false; int nextc = c-1; if (compareAndSetState(c,
nextc))// 使用CAS方法對計數(shù)器進行減1操作 return nextc == 0;//如果操作成功,返回計數(shù)器是否為0 } }
在await方法中,只調(diào)用了Sync實例的acquireSharedInterruptibly方法,具體是這樣的:
public void await() throws InterruptedException {
sync.acquireSharedInterruptibly(1); }
其中acquireSharedInterruptibly方法,判斷計數(shù)器是否為0,如果不為0則阻塞當前線程,具體是這樣的:
public final void acquireSharedInterruptibly(int arg) throws
InterruptedException { if (Thread.interrupted()) throw new
InterruptedException(); if (tryAcquireShared(arg) < 0)//判斷計數(shù)器是否為0
doAcquireSharedInterruptibly(arg);//如果不為0則阻塞當前線程 }
其中tryAcquireShared
方法,是AbstractQueuedSynchronizer中的一個模板方法,其具體實現(xiàn)在Sync類中,其主要是判斷計數(shù)器是否為零,如果為零則返回1,如果不為零則返回-1,具體是這樣的:
protected int tryAcquireShared(int acquires) { return (getState() == 0) ? 1 :
-1; }
面試官:嗯,很不錯,馬上給你發(fā)offer。
微信公眾號:萬貓學社
微信掃描二維碼
獲得更多Java技術干貨
熱門工具 換一換