一個長頭發、穿著清爽的小姐姐,拿著一個嶄新的Mac筆記本向我走來,看著來勢洶洶,我心想著肯定是技術大佬吧!但是我也是一個才華橫溢的人,穩住我們能贏,

面試官:看你簡歷上有寫熟悉并發編程,Semaphore一定用過吧,跟我說說它!
我:Semaphore是JDK提供的一個同步工具,它通過維護若干個許可證來控制執行緒對共享資源的訪問, 如果許可證剩余數量大于零時,執行緒則允許訪問該共享資源;如果許可證剩余數量為零時,則拒絕執行緒訪問該共享資源, Semaphore所維護的許可證數量就是允許訪問共享資源的最大執行緒數量, 所以,執行緒想要訪問共享資源必須從Semaphore中獲取到許可證,
面試官:Semaphore有哪些常用的方法?
我:有acquire方法和release方法, 當呼叫acquire方法時執行緒就會被阻塞,直到Semaphore中可以獲得到許可證為止,然后執行緒再獲取這個許可證, 當呼叫release方法時將向Semaphore中添加一個許可證,如果有執行緒因為獲取許可證被阻塞時,它將獲取到許可證并被釋放;如果沒有獲取許可證的執行緒, Semaphore只是記錄許可證的可用數量,
歡迎關注微信公眾號:萬貓學社,每周一分享Java技術干貨,
面試官:可以舉一個使用Semaphore的例子嗎?
我:張三、李四和王五和趙六4個人一起去飯店吃飯,不過在特殊時期洗手很重要,飯前洗手也是必須的,可是飯店只有2個洗手池,洗手池就是不能被同時使用的公共資源,這種場景就可以用到Semaphore,
面試官:可以簡單用代碼實作一下嗎?
我:當然可以,這是張三、李四、王五和趙六的顧客類:
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();
}
}
}
然后,寫一個測驗類模擬一下他們洗手的程序:
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 {
//飯店里只用兩個洗手池,所以初始化許可證的總數為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();
}
}
}
運行以后的結果應該是這樣的:
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 趙六 洗手完畢!
可以看到,當已經有兩個人在洗手的時候,其他人就被阻塞,直到有人洗手完畢才是開始洗手,
面試官:對Semaphore的內部原理有沒有了解?
我:Semaphore內部主要通過AQS(AbstractQueuedSynchronizer)實作執行緒的管理,Semaphore在構造時,需要傳入許可證的數量,它最后傳遞給了AQS的state值,執行緒在呼叫acquire方法獲取許可證時,如果Semaphore中許可證的數量大于0,許可證的數量就減1,執行緒繼續運行,當執行緒運行結束呼叫release方法時釋放許可證時,許可證的數量就加1,如果獲取許可證時,Semaphore中許可證的數量為0,則獲取失敗,執行緒進入AQS的等待佇列中,等待被其它釋放許可證的執行緒喚醒,
歡迎關注微信公眾號:萬貓學社,每周一分享Java技術干貨,
面試官:嗯,不錯,在您的代碼中,這4個人會按照執行緒啟動的順序洗手嘛?
我:不會按照執行緒啟動的順序洗手,有可能趙六比王五先洗手,
面試官:為什么會出現這種情況?
我:因為在我的代碼中,使用Semaphore的建構式是這個:
public Semaphore(int permits) {
sync = new NonfairSync(permits);
}
在這個建構式中,使用的是NonfairSync(非公平鎖),這個類不保證執行緒獲得許可證的順序,呼叫acquire方法的執行緒可以在一直等待的執行緒之前獲得一個許可證,
面試官:有沒有什么方法可保證他們的順序?
我:可以使用Semaphore的另一個建構式:
public Semaphore(int permits, boolean fair) {
sync = fair ? new FairSync(permits) : new NonfairSync(permits);
}
在呼叫構造方法時,fair引數傳入true,比如:
Semaphore washbasin = new Semaphore(2, true);
這樣使用的是FairSync(公平鎖),可以確保按照各個執行緒呼叫acquire方法的順序獲得許可證,
面試官:嗯,不錯,NonfairSync和FairSync有什么區別?為什么會造成這樣的效果?
我:這就涉及到NonfairSync和FairSync的內部實作了,
在NonfairSync中,acquire方法核心原始碼是:
final int nonfairTryAcquireShared(int acquires) {
//acquires引數默認為1,表示嘗試獲取1個許可證,
for (;;) {
int available = getState();
//remaining是剩余的許可數數量,
int remaining = available - acquires;
//剩余的許可數數量小于0時,
//當前執行緒進入AQS中的doAcquireSharedInterruptibly方法
//等待可用許可證并掛起,直到被喚醒,
if (remaining < 0 ||
compareAndSetState(available, remaining))
return remaining;
}
}
歡迎關注微信公眾號:萬貓學社,每周一分享Java技術干貨,
release方法核心原始碼是:
protected final boolean tryReleaseShared(int releases) {
//releases引數默認為1,表示嘗試釋放1個許可證,
for (;;) {
int current = getState();
//next是如果許可證釋放成功,可用許可證的數量,
int next = current + releases;
if (next < current) // overflow
throw new Error("Maximum permit count exceeded");
//如果許可證釋放成功,
//當前執行緒進入到AQS的doReleaseShared方法,
//喚醒佇列中等待許可的執行緒,
if (compareAndSetState(current, next))
return true;
}
}
當一個執行緒A呼叫acquire方法時,會直接嘗試獲取許可證,而不管同一時刻阻塞佇列中是否有執行緒也在等待許可證,如果恰好有執行緒C呼叫release方法釋放許可證,并喚醒阻塞佇列中第一個等待的執行緒B,此時執行緒A和執行緒B是共同競爭可用許可證,不公平性就體現在:執行緒A沒任何等待就和執行緒B一起競爭許可證了,
而在FairSync中,acquire方法核心原始碼是:
protected int tryAcquireShared(int acquires) {
//acquires引數默認為1,表示嘗試獲取1個許可證,
for (;;) {
//檢查阻塞佇列中是否有等待的執行緒
if (hasQueuedPredecessors())
return -1;
int available = getState();
//remaining是剩余的許可數數量,
int remaining = available - acquires;
if (remaining < 0 ||
compareAndSetState(available, remaining))
return remaining;
}
}
和非公平策略相比,FairSync中多一個對阻塞佇列是否有等待的執行緒的檢查,如果沒有,就可以參與許可證的競爭;如果有,執行緒直接被插入到阻塞佇列尾節點并掛起,等待被喚醒,
面試官:嗯,很不錯,馬上給你發offer,
本故事純屬虛構,如有雷同實屬巧合
微信公眾號:萬貓學社
微信掃描二維碼
獲得更多Java技術干貨
轉載請註明出處,本文鏈接:https://www.uj5u.com/houduan/169419.html
標籤:Java
