Semaphore 使用&核心原理 圖解
-
瘋狂創客圈 經典圖書 : 《Netty Zookeeper Redis 高併發實戰》 面試必備 + 面試必備 + 面試必備 【部落格園總入口 】
-
瘋狂創客圈 經典圖書 : 《SpringCloud、Nginx高併發核心程式設計》 大廠必備 + 大廠必備 + 大廠必備 【部落格園總入口 】
-
入大廠+漲工資必備: 高併發【 億級流量IM實戰】 實戰系列 【 SpringCloud Nginx秒殺】 實戰系列 【部落格園總入口 】
JUC 高併發工具類 3文章:
1 Semaphore是什麼?
Semaphore是計數訊號量。Semaphore管理一系列許可。每個acquire方法阻塞,直到有一個許可證可以獲得然後拿走一個許可證;每個release方法增加一個許可,這可能會釋放一個阻塞的acquire方法。然而,其實並沒有實際的許可這個物件,Semaphore只是維持了一個可獲得許可證的數量。
比如:停車場入口立著的那個螢幕,每有一輛車進入停車場螢幕就會顯示剩餘車位減1,每有一輛車從停車場出去,螢幕上顯示的剩餘車輛就會加1,當螢幕上的剩餘車位為0時,停車場入口的欄杆就不會再開啟,車輛就無法進入停車場了,直到有一輛車從停車場出去為止。
比如:在學生時代都去餐廳打過飯,假如有3個視窗可以打飯,同一時刻也只能有3名同學打飯。第四個人來了之後就必須在外面等著,只要有打飯的同學好了,就可以去相應的視窗了 。
[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片儲存下來直接上傳(img-fhj3NWT2-1604139314540)(https://pics5.baidu.com/feed/e1fe9925bc315c6004ec19b8f2c86e16485477ee.jpeg?token=4e4314eeaf79a48cc2cc9003bdf22d27&s=8D26F4170993F3E902C5DD4C03007073)]
2 怎麼使用 Semaphore
2.1 構造方法
//建立具有給定的許可數和非公平的公平設定的 Semaphore。
Semaphore(int permits)
//建立具有給定的許可數和給定的公平設定的 Semaphore。
Semaphore(int permits, boolean fair)
2.2 重要方法
在上面我們使用最基本的acquire方法和release方法就可以實現Semaphore最常見的功能,不過其他方法還是需要我們去了解一下的。
1、acquire(int permits)
從此訊號量獲取給定數目的許可,在提供這些許可前一直將執行緒阻塞,或者執行緒已被中斷。就好比是一個學生佔兩個視窗。這同時也對應了相應的release方法。
2、release(int permits)
釋放給定數目的許可,將其返回到訊號量。這個是對應於上面的方法,一個學生佔幾個視窗完事之後還要釋放多少
3、availablePermits()
返回此訊號量中當前可用的許可數。也就是返回當前還有多少個視窗可用。
4、reducePermits(int reduction)
根據指定的縮減量減小可用許可的數目。
5、hasQueuedThreads()
查詢是否有執行緒正在等待獲取資源。
6、getQueueLength()
返回正在等待獲取的執行緒的估計數目。該值僅是估計的數字。
7、tryAcquire(int permits, long timeout, TimeUnit unit)
如果在給定的等待時間內此訊號量有可用的所有許可,並且當前執行緒未被中斷,則從此訊號量獲取給定數目的許可。
8、acquireUninterruptibly(int permits)
從此訊號量獲取給定數目的許可,在提供這些許可前一直將執行緒阻塞。
3 使用案例
這個案例使用的就是我們之前的小例子,也就是去餐廳打飯的案例。
我們先看Test類:
[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片儲存下來直接上傳(img-FS6locSZ-1604139314543)(https://pics3.baidu.com/feed/472309f790529822787d1d77a9b3dece0b46d4b2.jpeg?token=4e2d7f2dfdf9be3f1a3f16fda56b967b&s=BA81A14C47F09868165999130000E081)]
在這個程式碼中我們看到,主要是new了一個Semaphore,然後賦給每一位同學Student,接下來我們就來好好看看Student執行緒是如何實現的。
[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片儲存下來直接上傳(img-ZiBxQx9P-1604139314544)(https://pics4.baidu.com/feed/2f738bd4b31c870154037c6659063b2a0608ff7d.jpeg?token=a01971796d2bdc9e96b2e1d0563fb96a&s=3281B14CD2B4966F56D9B50F000070C1)]
在這個Student類中我們最主要看run方法的實現,首先我們通過acquire獲取了當前視窗的許可,然後休眠3秒代表打飯,最後在finally使用release方法釋放這個視窗許可證。程式碼很簡單,原理很清楚,我們測試一波:
[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片儲存下來直接上傳(img-5jnNutK9-1604139314547)(https://pics0.baidu.com/feed/58ee3d6d55fbb2fbfe163bf6313385a14723dc90.jpeg?token=4dc06098df214c105b05dfe933528c9e&s=479C2D2A114F554F1C613CDA000050B4)]
這個結果你也看到了,基本上同一時刻只能有三個學生在視窗旁邊。
在這裡你可能有一個疑問了,Semaphore好像和synchronized關鍵字沒什麼區別,都可以實現同步,如果是這樣那說明我們還沒有真正理解jdk的註釋,他只是限制了訪問某些資源的執行緒數,其實並沒有實現同步,我們可以看一下:
[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片儲存下來直接上傳(img-O2oWAgxb-1604139314549)(https://pics7.baidu.com/feed/c995d143ad4bd113e91ab29724d6010a4afb053a.jpeg?token=195052e5fe3b9252fcc25d2ac11fe879&s=3A81A14C52F4AC69045D180B0000F0C0)]
現在我們在獲取許可前增加了一條輸出語句,也就是能列印出有哪個執行緒進入了,再去測試一波
[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片儲存下來直接上傳(img-mdrSNy4s-1604139314551)(https://pics4.baidu.com/feed/962bd40735fae6cd2ebff58a71caaa2143a70f54.jpeg?token=50bb6e1dd1815e4e7f1453196faf0cc6&s=F79CAD2B0FC24C4102CC75DE00008034)]
結果很清晰,所以對於Semaphore來說,我們需要記住的其實是資源的互斥而不是資源的同步,在同一時刻是無法保證同步的,但是卻可以保證資源的互斥。
4 Semaphore使用場景
用於那些資源有明確訪問數量限制的場景,常用於限流 。
-
比如:資料庫連線池,同時進行連線的執行緒有數量限制,連線不能超過一定的數量,當連線達到了限制數量後,後面的執行緒只能排隊等前面的執行緒釋放了資料庫連線才能獲得資料庫連線。
-
比如:停車場場景,車位數量有限,同時只能容納多少臺車,車位滿了之後只有等裡面的車離開停車場外面的車才可以進入。
5 Semaphore原理
(1)、Semaphore初始化。
Semaphore semaphore=new Semaphore(2);
1、當呼叫new Semaphore(2) 方法時,預設會建立一個非公平的鎖的同步阻塞佇列。
2、把初始許可數量賦值給同步佇列的state狀態,state的值就代表當前所剩餘的許可數量。
初始化完成後同步佇列資訊如下圖:
(2)獲取許可
semaphore.acquire();
1、當前執行緒會嘗試去同步佇列獲取一個許可,獲取許可的過程也就是使用原子的操作去修改同步佇列的state ,獲取一個許可則修改為state=state-1。
2、 當計算出來的state<0,則代表許可數量不足,此時會建立一個Node節點加入阻塞佇列,掛起當前執行緒。
3、當計算出來的state>=0,則代表獲取許可成功。
原始碼:
/**
* 獲取1個許可
*/
public void acquire() throws InterruptedException {
sync.acquireSharedInterruptibly(1);
}
/**
* 共享模式下獲取許可,獲取成功則返回,失敗則加入阻塞佇列,掛起執行緒
* @param arg
* @throws InterruptedException
*/
public final void acquireSharedInterruptibly(int arg)
throws InterruptedException {
if (Thread.interrupted())
throw new InterruptedException();
//嘗試獲取許可,arg為獲取許可個數,當可用許可數減當前許可數結果小於0,則建立一個節點加入阻塞佇列,掛起當前執行緒。
if (tryAcquireShared(arg) < 0)
doAcquireSharedInterruptibly(arg);
}
/**
* 1、建立節點,加入阻塞佇列,
* 2、重雙向連結串列的head,tail節點關係,清空無效節點
* 3、掛起當前節點執行緒
* @param arg
* @throws InterruptedException
*/
private void doAcquireSharedInterruptibly(int arg)
throws InterruptedException {
//建立節點加入阻塞佇列
final Node node = addWaiter(Node.SHARED);
boolean failed = true;
try {
for (;;) {
//獲得當前節點pre節點
final Node p = node.predecessor();
if (p == head) {
int r = tryAcquireShared(arg);//返回鎖的state
if (r >= 0) {
setHeadAndPropagate(node, r);
p.next = null; // help GC
failed = false;
return;
}
}
//重組雙向連結串列,清空無效節點,掛起當前執行緒
if (shouldParkAfterFailedAcquire(p, node) &&
parkAndCheckInterrupt())
throw new InterruptedException();
}
} finally {
if (failed)
cancelAcquire(node);
}
}
執行緒1、執行緒2、執行緒3、分別呼叫semaphore.acquire(),整個過程佇列資訊變化如下圖:
(3)、釋放許可
semaphore.release();
當呼叫semaphore.release() 方法時
1、執行緒會嘗試釋放一個許可,釋放許可的過程也就是把同步佇列的state修改為state=state+1的過程
2、釋放許可成功之後,同時會喚醒同步佇列的所有阻塞節共享節點執行緒
3、被喚醒的節點會重新嘗試去修改state=state-1 的操作,如果state>=0則獲取許可成功,否則重新進入阻塞佇列,掛起執行緒。
原始碼:
/**
* 釋放許可
*/
public void release() {
sync.releaseShared(1);
}
/**
*釋放共享鎖,同時喚醒所有阻塞佇列共享節點執行緒
* @param arg
* @return
*/
public final boolean releaseShared(int arg) {
//釋放共享鎖
if (tryReleaseShared(arg)) {
//喚醒所有共享節點執行緒
doReleaseShared();
return true;
}
return false;
}
/**
* 喚醒所有共享節點執行緒
*/
private void doReleaseShared() {
for (;;) {
Node h = head;
if (h != null && h != tail) {
int ws = h.waitStatus;
if (ws == Node.SIGNAL) {//是否需要喚醒後繼節點
if (!compareAndSetWaitStatus(h, Node.SIGNAL, 0))//修改狀態為初始0
continue;
unparkSuccessor(h);//喚醒h.nex節點執行緒
}
else if (ws == 0 &&
!compareAndSetWaitStatus(h, 0, Node.PROPAGATE));
}
if (h == head) // loop if head changed
break;
}
}
繼上面的圖,當我們執行緒1呼叫semaphore.release(); 時候整個流程如下圖:
回到◀瘋狂創客圈▶
瘋狂創客圈 - Java高併發研習社群,為大家開啟大廠之門
相關文章
- CyclicBarrier、CountDownLatch以及Semaphore使用及其原理分析CountDownLatch
- 圖解kubernetes控制器StatefulSet核心實現原理圖解
- 圖解 kubernetes 控制器 StatefulSet 核心實現原理圖解
- Semaphore使用案例
- 圖解Flutter——BLoC的原理及使用圖解FlutterBloC
- 深入瞭解Zookeeper核心原理
- Flink 核心元件 內部原理 多圖剖析元件
- 面試官:說說CountDownLatch,CyclicBarrier,Semaphore的原理?面試CountDownLatch
- 深度圖解Redis Cluster原理圖解Redis
- Netty核心原理Netty
- springboot核心原理Spring Boot
- Semaphore
- kafka核心原理的祕密,藏在這16張圖裡Kafka
- Struts2工作原理(圖解)圖解
- 圖解Go select語句原理圖解Go
- 清晰圖解深度分析HTTPS原理圖解HTTP
- CAS原理分析:併發程式設計核心中的核心你瞭解多少?程式設計
- Socket 核心原理分享
- 圖解 kubernetes 命令執行核心實現圖解
- 當面試官問我JDK Semaphore的原理時,我笑了面試JDK
- 併發工具類:Semaphore原始碼解讀原始碼
- Semaphore解析
- iMX6ULL 小尺寸核心板功能介紹|框架圖|功耗|核心板硬體設計說明|原理圖框架
- 圖解Java執行緒池原理圖解Java執行緒
- 圖解Go的channel底層原理圖解Go
- 圖解MongoDB叢集部署原理(3)圖解MongoDB
- 圖解:HTTP 範圍請求,助力斷點續傳、多執行緒下載的核心原理圖解HTTP斷點執行緒
- 精美圖文講解Java AQS 共享式獲取同步狀態以及Semaphore的應用JavaAQS
- binder核心原理解析
- Linux 核心101:cache原理Linux
- NameServer 核心原理解析Server
- 圖解kubernetes控制器Deployment核心機制圖解
- go中semaphore(訊號量)原始碼解讀Go原始碼
- 深入詳解Mybatis的架構原理與6大核心流程MyBatis架構
- 圖解WebGL和Three.js工作原理圖解WebJS
- 圖解git原理與日常實用指南圖解Git
- 圖解 K8s 核心概念和術語圖解K8S
- Java併發程式設計系列之Semaphore詳解Java程式設計