10.Semaphore

Semaphore

Semaphore 是一個計數(shù)信號量惭墓。從概念上講挺尿,信號量維護(hù)了一個許可集。如有必要饱苟,在許可可用前會阻塞每一個 acquire()孩擂,然后再獲取該許可。每個 release() 添加一個許可箱熬,從而可能釋放一個正在阻塞的獲取者类垦。但是,不使用實際的許可對象城须,Semaphore 只對可用許可的號碼進(jìn)行計數(shù)蚤认,并采取相應(yīng)的行動。

說白了糕伐,Semaphore是一個計數(shù)器砰琢,在計數(shù)器不為0的時候?qū)€程就放行,一旦達(dá)到0赤炒,那么所有請求資源的新線程都會被阻塞氯析,包括增加請求到許可的線程,也就是說Semaphore不是可重入的莺褒。每一次請求一個許可都會導(dǎo)致計數(shù)器減少1掩缓,同樣每次釋放一個許可都會導(dǎo)致計數(shù)器增加1遵岩,一旦達(dá)到了0,新的許可請求線程將被掛起舍哄。

緩存池整好使用此思想來實現(xiàn)的宴凉,比如鏈接池表悬、對象池等籽暇。

清單1 對象池

package xylz.study.concurrency.lock;

import java.util.concurrent.Semaphore;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

public class ObjectCache<T> {

? public interface ObjectFactory<T> {

? T makeObject();
? }

? class Node {

? T obj;

? Node next;
? }

? final int capacity;

? final ObjectFactory<T> factory;

? final Lock lock = new ReentrantLock();

? final Semaphore semaphore;

? private Node head;

? private Node tail;

? public ObjectCache(int capacity, ObjectFactory<T> factory) {
? this.capacity = capacity;
? this.factory = factory;
? this.semaphore = new Semaphore(this.capacity);
? this.head = null;
? this.tail = null;
? }

? public T getObject() throws InterruptedException {
? semaphore.acquire();
? return getNextObject();
? }

? private T getNextObject() {
? lock.lock();
? try {
? if (head == null) {
? return factory.makeObject();
? } else {
? Node ret = head;
? head = head.next;
? if (head == null) tail = null;
? ret.next = null;//help GC
? return ret.obj;
? }
? } finally {
? lock.unlock();
? }
? }

? private void returnObjectToPool(T t) {
? lock.lock();
? try {
? Node node = new Node();
? node.obj = t;
? if (tail == null) {
? head = tail = node;
? } else {
? tail.next = node;
? tail = node;
? }

? } finally {
? lock.unlock();
? }
? }

? public void returnObject(T t) {
? returnObjectToPool(t);
? semaphore.release();
? }

}

清單1描述了一個基于信號量Semaphore的對象池實現(xiàn)。此對象池最多支持capacity個對象寒矿,這在構(gòu)造函數(shù)中傳入。對象池有一個基于FIFO的隊列观挎,每次從對象池的頭結(jié)點開始取對象躏碳,如果頭結(jié)點為空就直接構(gòu)造一個新的對象返回。否則將頭結(jié)點對象取出,并且頭結(jié)點往后移動永乌。特別要說明的如果對象的個數(shù)用完了圈驼,那么新的線程將被阻塞,直到有對象被返回回來衙伶。返還對象時將對象加入FIFO的尾節(jié)點并且釋放一個空閑的信號量慌随,表示對象池中增加一個可用對象丸逸。

實際上對象池、線程池的原理大致上就是這樣的憔维,只不過真正的對象池舒萎、線程池要處理比較復(fù)雜的邏輯章鲤,所以實現(xiàn)起來還需要做很多的工作,例如超時機(jī)制萝挤,自動回收機(jī)制,對象的有效期等等問題。

這里特別說明的是信號量只是在信號不夠的時候掛起線程嫌拣,但是并不能保證信號量足夠的時候獲取對象和返還對象是線程安全的柔袁,所以在清單1中仍然需要鎖Lock來保證并發(fā)的正確性。

將信號量初始化為 1异逐,使得它在使用時最多只有一個可用的許可灰瞻,從而可用作一個相互排斥的鎖腥例。這通常也稱為二進(jìn)制信號量,因為它只能有兩種狀態(tài):一個可用的許可疏咐,或零個可用的許可缩举。按此方式使用時,二進(jìn)制信號量具有某種屬性(與很多 Lock 實現(xiàn)不同)印蓖,即可以由線程釋放“鎖”辽慕,而不是由所有者(因為信號量沒有所有權(quán)的概念)公浪。在某些專門的上下文(如死鎖恢復(fù))中這會很有用。

上面這段話的意思是說當(dāng)某個線程A持有信號量數(shù)為1的信號量時船侧,其它線程只能等待此線程釋放資源才能繼續(xù)欠气,這時候持有信號量的線程A就相當(dāng)于持有了“鎖”,其它線程的繼續(xù)就需要這把鎖镜撩,于是線程A的釋放才能決定其它線程的運行预柒,相當(dāng)于扮演了“鎖”的角色。

另外同公平鎖非公平鎖一樣袁梗,信號量也有公平性宜鸯。如果一個信號量是公平的表示線程在獲取信號量時按FIFO的順序得到許可,也就是按照請求的順序得到釋放遮怜。這里特別說明的是:所謂請求的順序是指在請求信號量而進(jìn)入FIFO隊列的順序淋袖,有可能某個線程先請求信號而后進(jìn)去請求隊列,那么次線程獲取信號量的順序就會晚于其后請求但是先進(jìn)入請求隊列的線程锯梁。這個在公平鎖和非公平鎖中談過很多即碗。

除了acquire以外,Semaphore還有幾種類似的acquire方法涝桅,這些方法可以更好的處理中斷和超時或者異步等特性拜姿,可以參考JDK API。

按照同樣的學(xué)習(xí)原則冯遂,下面對主要的實現(xiàn)進(jìn)行分析蕊肥。Semaphore的acquire方法實際上訪問的是AQSacquireSharedInterruptibly(arg)方法。這個可以參考CountDownLatch一節(jié)或者AQS一節(jié)蛤肌。

所以Semaphore的await實現(xiàn)也是比較簡單的壁却。與CountDownLatch不同的是,Semaphore區(qū)分公平信號和非公平信號裸准。

清單2 公平信號獲取方法

protected int tryAcquireShared(int acquires) {
? Thread current = Thread.currentThread();
? for (;;) {
? Thread first = getFirstQueuedThread();
? if (first != null && first != current)
? return -1;
? int available = getState();
? int remaining = available - acquires;
? if (remaining < 0 ||
? compareAndSetState(available, remaining))
? return remaining;
? }
}

清單3 非公平信號獲取方法

protected int tryAcquireShared(int acquires) {
? return nonfairTryAcquireShared(acquires);
}

final int nonfairTryAcquireShared(int acquires) {
? for (;;) {
? int available = getState();
? int remaining = available - acquires;
? if (remaining < 0 ||
? compareAndSetState(available, remaining))
? return remaining;
? }
}

對比清單2和清單3可以看到展东,公平信號和非公平信號在于第一次嘗試能否獲取信號時,公平信號量總是將當(dāng)前線程進(jìn)入AQS的CLH隊列進(jìn)行排隊(因為第一次嘗試時隊列的頭結(jié)點線程很有可能不是當(dāng)前線程炒俱,當(dāng)然不排除同一個線程第二次進(jìn)入信號量)盐肃,從而根據(jù)AQS的CLH隊列的順序FIFO依次獲取信號量;而對于非公平信號量权悟,第一次立即嘗試能否拿到信號量砸王,一旦信號量的剩余數(shù)available大于請求數(shù)(acquires通常為1),那么線程就立即得到了釋放峦阁,而不需要進(jìn)行AQS隊列進(jìn)行排隊谦铃。只有remaining<0的時候(也就是信號量不夠的時候)才會進(jìn)入AQS隊列。

所以非公平信號量的吞吐量總是要比公平信號量的吞吐量要大榔昔,但是需要強(qiáng)調(diào)的是非公平信號量和非公平鎖一樣存在“饑渴死”的現(xiàn)象驹闰,也就是說活躍線程可能總是拿到信號量瘪菌,而非活躍線程可能難以拿到信號量。而對于公平信號量由于總是靠請求的線程的順序來獲取信號量嘹朗,所以不存在此問題师妙。

參考資料:

  1. 信號量(Semaphore)在生產(chǎn)者和消費者模式的使用
  2. What is mutex and semaphore in Java ? What is the main difference ?
  3. 關(guān)于 java.util.concurrent 您不知道的 5 件事,第 2 部分
  4. Semahores
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末骡显,一起剝皮案震驚了整個濱河市疆栏,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌惫谤,老刑警劉巖壁顶,帶你破解...
    沈念sama閱讀 212,884評論 6 492
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異溜歪,居然都是意外死亡若专,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,755評論 3 385
  • 文/潘曉璐 我一進(jìn)店門蝴猪,熙熙樓的掌柜王于貴愁眉苦臉地迎上來调衰,“玉大人,你說我怎么就攤上這事自阱『坷颍” “怎么了?”我有些...
    開封第一講書人閱讀 158,369評論 0 348
  • 文/不壞的土叔 我叫張陵沛豌,是天一觀的道長趋箩。 經(jīng)常有香客問我,道長加派,這世上最難降的妖魔是什么叫确? 我笑而不...
    開封第一講書人閱讀 56,799評論 1 285
  • 正文 為了忘掉前任,我火速辦了婚禮芍锦,結(jié)果婚禮上竹勉,老公的妹妹穿的比我還像新娘。我一直安慰自己娄琉,他們只是感情好次乓,可當(dāng)我...
    茶點故事閱讀 65,910評論 6 386
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著孽水,像睡著了一般檬输。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上匈棘,一...
    開封第一講書人閱讀 50,096評論 1 291
  • 那天,我揣著相機(jī)與錄音析命,去河邊找鬼主卫。 笑死逃默,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的簇搅。 我是一名探鬼主播完域,決...
    沈念sama閱讀 39,159評論 3 411
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼瘩将!你這毒婦竟也來了吟税?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 37,917評論 0 268
  • 序言:老撾萬榮一對情侶失蹤姿现,失蹤者是張志新(化名)和其女友劉穎肠仪,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體备典,經(jīng)...
    沈念sama閱讀 44,360評論 1 303
  • 正文 獨居荒郊野嶺守林人離奇死亡异旧,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,673評論 2 327
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了提佣。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片吮蛹。...
    茶點故事閱讀 38,814評論 1 341
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖拌屏,靈堂內(nèi)的尸體忽然破棺而出潮针,到底是詐尸還是另有隱情,我是刑警寧澤倚喂,帶...
    沈念sama閱讀 34,509評論 4 334
  • 正文 年R本政府宣布每篷,位于F島的核電站,受9級特大地震影響务唐,放射性物質(zhì)發(fā)生泄漏雳攘。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 40,156評論 3 317
  • 文/蒙蒙 一枫笛、第九天 我趴在偏房一處隱蔽的房頂上張望吨灭。 院中可真熱鬧,春花似錦刑巧、人聲如沸喧兄。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,882評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽吠冤。三九已至,卻和暖如春恭理,著一層夾襖步出監(jiān)牢的瞬間拯辙,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,123評論 1 267
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點兒被人妖公主榨干…… 1. 我叫王不留涯保,地道東北人诉濒。 一個月前我還...
    沈念sama閱讀 46,641評論 2 362
  • 正文 我出身青樓,卻偏偏與公主長得像夕春,于是被迫代替她去往敵國和親未荒。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 43,728評論 2 351

推薦閱讀更多精彩內(nèi)容