一個(gè)長(zhǎng)頭發(fā)、穿著清爽的小姐姐,拿著一個(gè)嶄新的Mac筆記本向我走來,看著來勢(shì)洶洶,我心想著肯定是技術(shù)大佬吧!但是我也是一個(gè)才華橫溢的人,穩(wěn)住我們能贏。



          面試官:看你簡(jiǎn)歷上有寫熟悉并發(fā)編程,Semaphore一定用過吧,跟我說說它!

          我:Semaphore是JDK提供的一個(gè)同步工具,它通過維護(hù)若干個(gè)許可證來控制線程對(duì)共享資源的訪問。
          如果許可證剩余數(shù)量大于零時(shí),線程則允許訪問該共享資源;如果許可證剩余數(shù)量為零時(shí),則拒絕線程訪問該共享資源。
          Semaphore所維護(hù)的許可證數(shù)量就是允許訪問共享資源的最大線程數(shù)量。 所以,線程想要訪問共享資源必須從Semaphore中獲取到許可證。

          面試官:Semaphore有哪些常用的方法?

          我:有acquire方法和release方法。 當(dāng)調(diào)用acquire
          方法時(shí)線程就會(huì)被阻塞,直到Semaphore中可以獲得到許可證為止,然后線程再獲取這個(gè)許可證。 當(dāng)調(diào)用release
          方法時(shí)將向Semaphore中添加一個(gè)許可證,如果有線程因?yàn)楂@取許可證被阻塞時(shí),它將獲取到許可證并被釋放;如果沒有獲取許可證的線程,
          Semaphore只是記錄許可證的可用數(shù)量。

          歡迎關(guān)注微信公眾號(hào):萬貓學(xué)社,每周一分享Java技術(shù)干貨。

          面試官:可以舉一個(gè)使用Semaphore的例子嗎?


          :張三、李四和王五和趙六4個(gè)人一起去飯店吃飯,不過在特殊時(shí)期洗手很重要,飯前洗手也是必須的,可是飯店只有2個(gè)洗手池,洗手池就是不能被同時(shí)使用的公共資源,這種場(chǎng)景就可以用到Semaphore。

          面試官:可以簡(jiǎn)單用代碼實(shí)現(xiàn)一下嗎?

          我:當(dāng)然可以,這是張三、李四、王五和趙六的顧客類:
          package onemore.study.semaphore; import java.text.SimpleDateFormat; import
          java.util.Date; import java.util.Random; import
          java.util.concurrent.CountDownLatch; import java.util.concurrent.Semaphore;
          public class Customer implements Runnable { private Semaphore washbasin;
          private String name; public Customer(Semaphore washbasin, String name) {
          this.washbasin = washbasin; this.name = name; } @Override public void run() {
          try { SimpleDateFormat sdf = new SimpleDateFormat("HH:mm:ss.SSS"); Random
          random = new Random(); washbasin.acquire(); System.out.println( sdf.format(new
          Date()) + " " + name + " 開始洗手..."); Thread.sleep((long) (random.nextDouble() *
          5000) + 2000); System.out.println( sdf.format(new Date()) + " " + name + "
          洗手完畢!"); washbasin.release(); } catch (Exception e) { e.printStackTrace(); } } }
          然后,寫一個(gè)測(cè)試類模擬一下他們洗手的過程:
          package onemore.study.semaphore; import java.util.ArrayList; import
          java.util.List; import java.util.concurrent.Semaphore; public class
          SemaphoreTester { public static void main(String[] args) throws
          InterruptedException { //飯店里只用兩個(gè)洗手池,所以初始化許可證的總數(shù)為2。 Semaphore washbasin = new
          Semaphore(2); List<Thread> threads = new ArrayList<>(3); threads.add(new
          Thread(new Customer(washbasin, "張三"))); threads.add(new Thread(new
          Customer(washbasin, "李四"))); threads.add(new Thread(new Customer(washbasin,
          "王五"))); threads.add(new Thread(new Customer(washbasin, "趙六"))); for (Thread
          thread : threads) { thread.start(); Thread.sleep(50); } for (Thread thread :
          threads) { thread.join(); } } }
          運(yùn)行以后的結(jié)果應(yīng)該是這樣的:
          06:51:54.416 李四 開始洗手... 06:51:54.416 張三 開始洗手... 06:51:57.251 張三 洗手完畢!
          06:51:57.251 王五 開始洗手... 06:51:59.418 李四 洗手完畢! 06:51:59.418 趙六 開始洗手...
          06:52:02.496 王五 洗手完畢! 06:52:06.162 趙六 洗手完畢!
          可以看到,當(dāng)已經(jīng)有兩個(gè)人在洗手的時(shí)候,其他人就被阻塞,直到有人洗手完畢才是開始洗手。

          面試官:對(duì)Semaphore的內(nèi)部原理有沒有了解?


          :Semaphore內(nèi)部主要通過AQS(AbstractQueuedSynchronizer)實(shí)現(xiàn)線程的管理。Semaphore在構(gòu)造時(shí),需要傳入許可證的數(shù)量,它最后傳遞給了AQS的state值。線程在調(diào)用
          acquire方法獲取許可證時(shí),如果Semaphore中許可證的數(shù)量大于0,許可證的數(shù)量就減1,線程繼續(xù)運(yùn)行,當(dāng)線程運(yùn)行結(jié)束調(diào)用release
          方法時(shí)釋放許可證時(shí),許可證的數(shù)量就加1。如果獲取許可證時(shí),Semaphore中許可證的數(shù)量為0,則獲取失敗,線程進(jìn)入AQS的等待隊(duì)列中,等待被其它釋放許可證的線程喚醒。

          歡迎關(guān)注微信公眾號(hào):萬貓學(xué)社,每周一分享Java技術(shù)干貨。

          面試官:嗯,不錯(cuò)。在您的代碼中,這4個(gè)人會(huì)按照線程啟動(dòng)的順序洗手嘛?

          我:不會(huì)按照線程啟動(dòng)的順序洗手,有可能趙六比王五先洗手。

          面試官:為什么會(huì)出現(xiàn)這種情況?

          我:因?yàn)樵谖业拇a中,使用Semaphore的構(gòu)造函數(shù)是這個(gè):
          public Semaphore(int permits) { sync = new NonfairSync(permits); }
          在這個(gè)構(gòu)造函數(shù)中,使用的是NonfairSync(非公平鎖),這個(gè)類不保證線程獲得許可證的順序,調(diào)用acquire
          方法的線程可以在一直等待的線程之前獲得一個(gè)許可證。

          面試官:有沒有什么方法可保證他們的順序?

          我:可以使用Semaphore的另一個(gè)構(gòu)造函數(shù):
          public Semaphore(int permits, boolean fair) { sync = fair ? new
          FairSync(permits) : new NonfairSync(permits); }
          在調(diào)用構(gòu)造方法時(shí),fair參數(shù)傳入true,比如:
          Semaphore washbasin = new Semaphore(2, true);
          這樣使用的是FairSync(公平鎖),可以確保按照各個(gè)線程調(diào)用acquire方法的順序獲得許可證。

          面試官:嗯,不錯(cuò)。NonfairSync和FairSync有什么區(qū)別?為什么會(huì)造成這樣的效果?

          我:這就涉及到NonfairSync和FairSync的內(nèi)部實(shí)現(xiàn)了。

          在NonfairSync中,acquire方法核心源碼是:
          final int nonfairTryAcquireShared(int acquires) {
          //acquires參數(shù)默認(rèn)為1,表示嘗試獲取1個(gè)許可證。 for (;;) { int available = getState();
          //remaining是剩余的許可數(shù)數(shù)量。 int remaining = available - acquires; //剩余的許可數(shù)數(shù)量小于0時(shí),
          //當(dāng)前線程進(jìn)入AQS中的doAcquireSharedInterruptibly方法 //等待可用許可證并掛起,直到被喚醒。 if (remaining <
          0 || compareAndSetState(available, remaining)) return remaining; } }
          歡迎關(guān)注微信公眾號(hào):萬貓學(xué)社,每周一分享Java技術(shù)干貨。

          release方法核心源碼是:
          protected final boolean tryReleaseShared(int releases) {
          //releases參數(shù)默認(rèn)為1,表示嘗試釋放1個(gè)許可證。 for (;;) { int current = getState();
          //next是如果許可證釋放成功,可用許可證的數(shù)量。 int next = current + releases; if (next < current)
          // overflow throw new Error("Maximum permit count exceeded"); //如果許可證釋放成功,
          //當(dāng)前線程進(jìn)入到AQS的doReleaseShared方法, //喚醒隊(duì)列中等待許可的線程。 if (compareAndSetState(current,
          next)) return true; } }
          當(dāng)一個(gè)線程A調(diào)用acquire方法時(shí),會(huì)直接嘗試獲取許可證,而不管同一時(shí)刻阻塞隊(duì)列中是否有線程也在等待許可證,如果恰好有線程C調(diào)用release
          方法釋放許可證,并喚醒阻塞隊(duì)列中第一個(gè)等待的線程B,此時(shí)線程A和線程B是共同競(jìng)爭(zhēng)可用許可證,不公平性就體現(xiàn)在:線程A沒任何等待就和線程B一起競(jìng)爭(zhēng)許可證了。

          而在FairSync中,acquire方法核心源碼是:
          protected int tryAcquireShared(int acquires) { //acquires參數(shù)默認(rèn)為1,表示嘗試獲取1個(gè)許可證。
          for (;;) { //檢查阻塞隊(duì)列中是否有等待的線程 if (hasQueuedPredecessors()) return -1; int
          available = getState(); //remaining是剩余的許可數(shù)數(shù)量。 int remaining = available -
          acquires; if (remaining < 0 || compareAndSetState(available, remaining)) return
          remaining; } }

          和非公平策略相比,F(xiàn)airSync中多一個(gè)對(duì)阻塞隊(duì)列是否有等待的線程的檢查,如果沒有,就可以參與許可證的競(jìng)爭(zhēng);如果有,線程直接被插入到阻塞隊(duì)列尾節(jié)點(diǎn)并掛起,等待被喚醒。

          面試官:嗯,很不錯(cuò),馬上給你發(fā)offer。

          本故事純屬虛構(gòu),如有雷同實(shí)屬巧合

          微信公眾號(hào):萬貓學(xué)社

          微信掃描二維碼

          獲得更多Java技術(shù)干貨

          友情鏈接
          ioDraw流程圖
          API參考文檔
          OK工具箱
          云服務(wù)器優(yōu)惠
          阿里云優(yōu)惠券
          騰訊云優(yōu)惠券
          京東云優(yōu)惠券
          站點(diǎn)信息
          問題反饋
          郵箱:[email protected]
          QQ群:637538335
          關(guān)注微信

                中国一级片在线 | 美女激情影院 | 男女男网站 | 亚洲操屄 | 国产又猛又粗又黄又爽 |