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