摘要:當(dāng)線程使用完共享資源后,可以歸還許可,以供其它需要的線程使用。所以,并不會(huì)阻塞調(diào)用線程。立即減少指定數(shù)目的可用許可數(shù)。方法用于將可用許可數(shù)清零,并返回清零前的許可數(shù)六的類接口聲明類聲明構(gòu)造器接口聲明
本文首發(fā)于一世流云的專欄:https://segmentfault.com/blog...一、Semaphore簡介
Semaphore,又名信號(hào)量,這個(gè)類的作用有點(diǎn)類似于“許可證”。有時(shí),我們因?yàn)橐恍┰蛐枰刂仆瑫r(shí)訪問共享資源的最大線程數(shù)量,比如出于系統(tǒng)性能的考慮需要限流,或者共享資源是稀缺資源,我們需要有一種辦法能夠協(xié)調(diào)各個(gè)線程,以保證合理的使用公共資源。
Semaphore維護(hù)了一個(gè)許可集,其實(shí)就是一定數(shù)量的“許可證”。
當(dāng)有線程想要訪問共享資源時(shí),需要先獲取(acquire)的許可;如果許可不夠了,線程需要一直等待,直到許可可用。當(dāng)線程使用完共享資源后,可以歸還(release)許可,以供其它需要的線程使用。
另外,Semaphore支持公平/非公平策略,這和ReentrantLock類似,后面講Semaphore原理時(shí)會(huì)看到,它們的實(shí)現(xiàn)本身就是類似的。
二、Semaphore示例我們來看下Oracle官方給出的示例:
class Pool { private static final int MAX_AVAILABLE = 100; // 可同時(shí)訪問資源的最大線程數(shù) private final Semaphore available = new Semaphore(MAX_AVAILABLE, true); protected Object[] items = new Object[MAX_AVAILABLE]; //共享資源 protected boolean[] used = new boolean[MAX_AVAILABLE]; public Object getItem() throws InterruptedException { available.acquire(); return getNextAvailableItem(); } public void putItem(Object x) { if (markAsUnused(x)) available.release(); } private synchronized Object getNextAvailableItem() { for (int i = 0; i < MAX_AVAILABLE; ++i) { if (!used[i]) { used[i] = true; return items[i]; } } return null; } private synchronized boolean markAsUnused(Object item) { for (int i = 0; i < MAX_AVAILABLE; ++i) { if (item == items[i]) { if (used[i]) { used[i] = false; return true; } else return false; } } return false; } }
items數(shù)組可以看成是我們的共享資源,當(dāng)有線程嘗試使用共享資源時(shí),我們要求線程先獲得“許可”(調(diào)用Semaphore 的acquire方法),這樣線程就擁有了權(quán)限,否則就需要等待。當(dāng)使用完資源后,線程需要調(diào)用Semaphore 的release方法釋放許可。
注意:上述示例中,對(duì)于共享資源訪問需要由鎖來控制,Semaphore僅僅是保證了線程由權(quán)限使用共享資源,至于使用過程中是否由并發(fā)問題,需要通過鎖來保證。
總結(jié)一下,許可數(shù) ≤ 0代表共享資源不可用。許可數(shù) > 0,代表共享資源可用,且多個(gè)線程可以同時(shí)訪問共享資源。
這是不是和CountDownLatch有點(diǎn)像?
我們來比較下:
同步器 | 作用 |
---|---|
CountDownLatch | 同步狀態(tài)State > 0表示資源不可用,所有線程需要等待;State == 0表示資源可用,所有線程可以同時(shí)訪問 |
Semaphore | 剩余許可數(shù) < 0表示資源不可用,所有線程需要等待; 許可剩余數(shù) ≥ 0表示資源可用,所有線程可以同時(shí)訪問 |
如果讀者閱讀過本系列的AQS相關(guān)文章,應(yīng)該立馬可以反應(yīng)過來,這其實(shí)就是對(duì)同步狀態(tài)的定義不同。三、Semaphore原理 Semaphore的內(nèi)部結(jié)構(gòu)
CountDownLatch內(nèi)部實(shí)現(xiàn)了AQS的共享功能,那么Semaphore是否也一樣是利用內(nèi)部類實(shí)現(xiàn)了AQS的共享功能呢?
我們先來看下Semaphore的內(nèi)部:
可以看到,Semaphore果然是通過內(nèi)部類實(shí)現(xiàn)了AQS框架提供的接口,而且基本結(jié)構(gòu)幾乎和ReentrantLock完全一樣,通過內(nèi)部類分別實(shí)現(xiàn)了公平/非公平策略。
Semaphore對(duì)象的構(gòu)造Semaphore sm = new Semaphore (3, true);
Semaphore有兩個(gè)構(gòu)造器:
構(gòu)造器1:
構(gòu)造器2:
構(gòu)造時(shí)需要指定“許可”的數(shù)量——permits,內(nèi)部結(jié)構(gòu)如下:
我們還是通過示例來分析:
假設(shè)現(xiàn)在一共3個(gè)線程:ThreadA、ThreadB、ThreadC。一個(gè)許可數(shù)為2的公平策略的Semaphore。線程的調(diào)用順序如下:
Semaphore sm = new Semaphore (2, true); // ThreadA: sm.acquire() // ThreadB: sm.acquire(2) // ThreadC: sm.acquire() // ThreadA: sm.release() // ThreadB: sm.release(2)創(chuàng)建公平策略的Semaphore對(duì)象
Semaphore sm = new Semaphore (2, true);
可以看到,內(nèi)部創(chuàng)建了一個(gè)FairSync對(duì)象,并傳入許可數(shù)permits:
Sync是Semaphore的一個(gè)內(nèi)部抽象類,公平策略的FairSync和非公平策略的NonFairSync都繼承該類。
可以看到,構(gòu)造器傳入的permits值就是同步狀態(tài)的值,這也體現(xiàn)了我們?cè)贏QS系列中說過的:
AQS框架的設(shè)計(jì)思想就是分離構(gòu)建同步器時(shí)的一系列關(guān)注點(diǎn),它的所有操作都圍繞著資源——同步狀態(tài)(synchronization state)來展開,并將資源的定義和訪問留給用戶解決:
Semaphore的acquire方法內(nèi)部調(diào)用了AQS的方法,入?yún)?1"表示嘗試獲取1個(gè)許可:
AQS的acquireSharedInterruptibly方式是共享功能的一部分,我們?cè)贏QS系列中就已經(jīng)對(duì)它很熟悉了:
關(guān)鍵來看下Semaphore是如何實(shí)現(xiàn)tryAcquireShared方法的:
對(duì)于Semaphore來說,線程是可以一次性嘗試獲取多個(gè)許可的,此時(shí)只要剩余的許可數(shù)量夠,最終會(huì)通過自旋操作更新成功。如果剩余許可數(shù)量不夠,會(huì)返回一個(gè)負(fù)數(shù),表示獲取失敗。
顯然,ThreadA獲取許可成功。此時(shí),同步狀態(tài)值State == 1,等待隊(duì)列的結(jié)構(gòu)如下:
帶入?yún)⒌?strong>aquire方法內(nèi)部和無參的一樣,都是調(diào)用了AQS的acquireSharedInterruptibly方法:
此時(shí),ThreadB一樣進(jìn)入tryAcquireShared方法。不同的是,此時(shí)剩余許可數(shù)不足,因?yàn)門hreadB一次性獲取2個(gè)許可,tryAcquireShared方法返回一個(gè)負(fù)數(shù),表示獲取失敗:
remaining = available - acquires = 1- 2 = -1;
ThreadB會(huì)調(diào)用doAcquireSharedInterruptibly方法:
上述方法首先通過addWaiter方法將ThreadB包裝成一個(gè)共享結(jié)點(diǎn),加入等待隊(duì)列:
然后會(huì)進(jìn)入自旋操作,先嘗試獲取一次資源,顯然此時(shí)是獲取失敗的,然后判斷是否要進(jìn)入阻塞(shouldParkAfterFailedAcquire):
上述方法會(huì)先將前驅(qū)結(jié)點(diǎn)的狀態(tài)置為SIGNAL,表示ThreadB需要阻塞,但在阻塞之前需要將前驅(qū)置為SIGNAL,以便將來可以喚醒ThreadB。
最終ThreadB會(huì)在parkAndCheckInterrupt中進(jìn)入阻塞:
此時(shí),同步狀態(tài)值依然是State == 1,等待隊(duì)列的結(jié)構(gòu)如下:
流程和步驟3完全相同,ThreadC被包裝成結(jié)點(diǎn)加入等待隊(duì)列后:
同步狀態(tài):State == 1
ThreadA調(diào)用release()方法Semaphore的realse方法調(diào)用了AQS的releaseShared方法,默認(rèn)入?yún)?1",表示歸還一個(gè)許可:
來看下Semaphore是如何實(shí)現(xiàn)tryReleaseShared方法的,tryReleaseShared方法是一個(gè)自旋操作,直到更新State成功:
更新完成后,State == 2,ThreadA會(huì)進(jìn)入doReleaseShared方法,先將頭結(jié)點(diǎn)狀態(tài)置為0,表示即將喚醒后繼結(jié)點(diǎn):
此時(shí),等待隊(duì)列結(jié)構(gòu):
然后調(diào)用unparkSuccessor方法喚醒后繼結(jié)點(diǎn):
此時(shí),ThreadB被喚醒,會(huì)從原阻塞處繼續(xù)向下執(zhí)行:
此時(shí),同步狀態(tài):State == 2
ThreadB從原阻塞處繼續(xù)執(zhí)行ThreadB被喚醒后,從下面開始繼續(xù)往下執(zhí)行,進(jìn)入下一次自旋:
在下一次自旋中,ThreadB調(diào)用tryAcquireShared方法成功獲取到共享資源(State修改為0),setHeadAndPropagate方法把ThreadB變?yōu)轭^結(jié)點(diǎn),
并根據(jù)傳播狀態(tài)判斷是否要喚醒并釋放后繼結(jié)點(diǎn):
同步狀態(tài):State == 0
ThreadB會(huì)調(diào)用doReleaseShared方法,繼續(xù)嘗試喚醒后繼的共享結(jié)點(diǎn)(也就是ThreadC),這個(gè)過程和ThreadB被喚醒完全一樣:
同步狀態(tài):State == 0
ThreadC從原阻塞處繼續(xù)執(zhí)行由于目前共享資源仍為0,所以ThreadC被喚醒后,在經(jīng)過嘗試獲取資源失敗后,又進(jìn)入了阻塞:
內(nèi)部和無參的release方法一樣:
更新完成后,State == 2,ThreadA會(huì)進(jìn)入doReleaseShared方法,喚醒后繼結(jié)點(diǎn):
此時(shí),等待隊(duì)列結(jié)構(gòu):
同步狀態(tài):State == 2
ThreadC從原阻塞處繼續(xù)執(zhí)行由于目前共享資源為2,所以ThreadC被喚醒后,獲取資源成功:
最終同步隊(duì)列的結(jié)構(gòu)如下:
同步狀態(tài):State == 0
五、總結(jié)Semaphore其實(shí)就是實(shí)現(xiàn)了AQS共享功能的同步器,對(duì)于Semaphore來說,資源就是許可證的數(shù)量:
剩余許可證數(shù)(State值) - 嘗試獲取的許可數(shù)(acquire方法入?yún)ⅲ?≥ 0:資源可用
剩余許可證數(shù)(State值) - 嘗試獲取的許可數(shù)(acquire方法入?yún)ⅲ? < 0:資源不可用
這里共享的含義是多個(gè)線程可以同時(shí)獲取資源,當(dāng)計(jì)算出的剩余資源不足時(shí),線程就會(huì)阻塞。
注意:Semaphore不是鎖,只能限制同時(shí)訪問資源的線程數(shù),至于對(duì)數(shù)據(jù)一致性的控制,Semaphore是不關(guān)心的。當(dāng)前,如果是只有一個(gè)許可的Semaphore,可以當(dāng)作鎖使用。Semaphore的非公平策略
另外,上述我們討論的是Semaphore的公平策略,非公平策略的差異并不大:
可以看到,非公平策略不會(huì)去查看等待隊(duì)列的隊(duì)首是否有其它線程正在等待,而是直接嘗試修改State值。
Semaphore的其它方法Semaphore還有兩個(gè)比較特殊的方法,這兩個(gè)方法的特點(diǎn)是采用自旋操作State變量,直到成功為止。所以,并不會(huì)阻塞調(diào)用線程。
reducePermits
reducePermits立即減少指定數(shù)目的可用許可數(shù)。
drainPermits
drainPermits方法用于將可用許可數(shù)清零,并返回清零前的許可數(shù)
六、Semaphore的類/接口聲明 類聲明 構(gòu)造器 接口聲明文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請(qǐng)注明本文地址:http://specialneedsforspecialkids.com/yun/76652.html
摘要:整個(gè)包,按照功能可以大致劃分如下鎖框架原子類框架同步器框架集合框架執(zhí)行器框架本系列將按上述順序分析,分析所基于的源碼為。后,根據(jù)一系列常見的多線程設(shè)計(jì)模式,設(shè)計(jì)了并發(fā)包,其中包下提供了一系列基礎(chǔ)的鎖工具,用以對(duì)等進(jìn)行補(bǔ)充增強(qiáng)。 showImg(https://segmentfault.com/img/remote/1460000016012623); 本文首發(fā)于一世流云專欄:https...
摘要:初始時(shí),為,當(dāng)調(diào)用方法時(shí),線程的加,當(dāng)調(diào)用方法時(shí),如果為,則調(diào)用線程進(jìn)入阻塞狀態(tài)。該對(duì)象一般供監(jiān)視診斷工具確定線程受阻塞的原因時(shí)使用。 showImg(https://segmentfault.com/img/remote/1460000016012503); 本文首發(fā)于一世流云的專欄:https://segmentfault.com/blog... 一、LockSupport類簡介...
摘要:在時(shí),引入了包,該包中的大多數(shù)同步器都是基于來構(gòu)建的。框架提供了一套通用的機(jī)制來管理同步狀態(tài)阻塞喚醒線程管理等待隊(duì)列。指針用于在結(jié)點(diǎn)線程被取消時(shí),讓當(dāng)前結(jié)點(diǎn)的前驅(qū)直接指向當(dāng)前結(jié)點(diǎn)的后驅(qū)完成出隊(duì)動(dòng)作。 showImg(https://segmentfault.com/img/remote/1460000016012438); 本文首發(fā)于一世流云的專欄:https://segmentfau...
摘要:線程可以調(diào)用的方法進(jìn)入阻塞,當(dāng)計(jì)數(shù)值降到時(shí),所有之前調(diào)用阻塞的線程都會(huì)釋放。注意的初始計(jì)數(shù)值一旦降到,無法重置。 showImg(https://segmentfault.com/img/remote/1460000016012041); 本文首發(fā)于一世流云的專欄:https://segmentfault.com/blog... 一、CountDownLatch簡介 CountDow...
摘要:二接口簡介可以看做是類的方法的替代品,與配合使用。當(dāng)線程執(zhí)行對(duì)象的方法時(shí),當(dāng)前線程會(huì)立即釋放鎖,并進(jìn)入對(duì)象的等待區(qū),等待其它線程喚醒或中斷。 showImg(https://segmentfault.com/img/remote/1460000016012601); 本文首發(fā)于一世流云的專欄:https://segmentfault.com/blog... 本系列文章中所說的juc-...
閱讀 2538·2023-04-26 00:57
閱讀 911·2021-11-25 09:43
閱讀 2221·2021-11-11 16:55
閱讀 2207·2019-08-30 15:53
閱讀 3592·2019-08-30 15:52
閱讀 1459·2019-08-30 14:10
閱讀 3379·2019-08-30 13:22
閱讀 1209·2019-08-29 11:18