【死磕Java并發(fā)】-----J.U.C之并發(fā)工具類:Semaphore

此篇博客所有源碼均來自JDK 1.8

信號量Semaphore是一個控制訪問多個共享資源的計數(shù)器,和CountDownLatch一樣中燥,其本質(zhì)上是一個“共享鎖”塘偎。

Semaphore,在API是這么介紹的:

一個計數(shù)信號量咱扣。從概念上講峰尝,信號量維護(hù)了一個許可集。如有必要祭往,在許可可用前會阻塞每一個 acquire()火窒,然后再獲取該許可。每個 release() 添加一個許可已骇,從而可能釋放一個正在阻塞的獲取者票编。但是,不使用實際的許可對象鲤竹,Semaphore 只對可用許可的號碼進(jìn)行計數(shù)昔榴,并采取相應(yīng)的行動。

Semaphore 通常用于限制可以訪問某些資源(物理或邏輯的)的線程數(shù)目吱肌。

下面我們就一個停車場的簡單例子來闡述Semaphore:

為了簡單起見我們假設(shè)停車場僅有5個停車位仰禽,一開始停車場沒有車輛所有車位全部空著纺蛆,然后先后到來三輛車规揪,停車場車位夠,安排進(jìn)去停車识颊,然后又來三輛奕坟,這個時候由于只有兩個停車位,所有只能停兩輛刃跛,其余一輛必須在外面候著苛萎,直到停車場有空車位,當(dāng)然以后每來一輛都需要在外面候著蛙酪。當(dāng)停車場有車開出去翘盖,里面有空位了,則安排一輛車進(jìn)去(至于是哪輛 要看選擇的機(jī)制是公平還是非公平)阁危。

從程序角度看汰瘫,停車場就相當(dāng)于信號量Semaphore,其中許可數(shù)為5趴乡,車輛就相對線程剑逃。當(dāng)來一輛車時官辽,許可數(shù)就會減 1 同仆,當(dāng)停車場沒有車位了(許可書 == 0 ),其他來的車輛需要在外面等候著。如果有一輛車開出停車場市怎,許可數(shù) + 1辛慰,然后放進(jìn)來一輛車。

號量Semaphore是一個非負(fù)整數(shù)(>=1)驰弄。當(dāng)一個線程想要訪問某個共享資源時速客,它必須要先獲取Semaphore,當(dāng)Semaphore >0時岔擂,獲取該資源并使Semaphore – 1浪耘。如果Semaphore值 = 0七冲,則表示全部的共享資源已經(jīng)被其他線程全部占用,線程必須要等待其他線程釋放資源癞埠。當(dāng)線程釋放資源時苗踪,Semaphore則+1

實現(xiàn)分析

Semaphore結(jié)構(gòu)如下:

[圖片上傳中。通铲。颅夺。(1)]

從上圖可以看出Semaphore內(nèi)部包含公平鎖(FairSync)和非公平鎖(NonfairSync),繼承內(nèi)部類Sync部服,其中Sync繼承AQS(再一次闡述AQS的重要性)拗慨。

Semaphore提供了兩個構(gòu)造函數(shù):

  1. Semaphore(int permits) :創(chuàng)建具有給定的許可數(shù)和非公平的公平設(shè)置的 Semaphore奉芦。 2. Semaphore(int permits, boolean fair) :創(chuàng)建具有給定的許可數(shù)和給定的公平設(shè)置的 Semaphore剧蹂。

實現(xiàn)如下:

    public Semaphore(int permits) {
        sync = new NonfairSync(permits);
    }

    public Semaphore(int permits, boolean fair) {
        sync = fair ? new FairSync(permits) : new NonfairSync(permits);
    }

Semaphore默認(rèn)選擇非公平鎖宠叼。

當(dāng)信號量Semaphore = 1 時,它可以當(dāng)作互斥鎖使用筹裕。其中0窄驹、1就相當(dāng)于它的狀態(tài),當(dāng)=1時表示其他線程可以獲取抗斤,當(dāng)=0時丈咐,排他,即其他線程必須要等待伤疙。

信號量獲取

Semaphore提供了acquire()方法來獲取一個許可辆影。

    public void acquire() throws InterruptedException {
        sync.acquireSharedInterruptibly(1);
    }

內(nèi)部調(diào)用AQS的acquireSharedInterruptibly(int arg),該方法以共享模式獲取同步狀態(tài):

    public final void acquireSharedInterruptibly(int arg)
            throws InterruptedException {
        if (Thread.interrupted())
            throw new InterruptedException();
        if (tryAcquireShared(arg) < 0)
            doAcquireSharedInterruptibly(arg);
    }

在acquireSharedInterruptibly(int arg)中锯蛀,tryAcquireShared(int arg)由子類來實現(xiàn)旁涤,對于Semaphore而言迫像,如果我們選擇非公平模式,則調(diào)用NonfairSync的tryAcquireShared(int arg)方法菌羽,否則調(diào)用FairSync的tryAcquireShared(int arg)方法纷闺。

公平

    protected int tryAcquireShared(int acquires) {
        for (;;) {
            //判斷該線程是否位于CLH隊列的列頭
            if (hasQueuedPredecessors())
                return -1;
            //獲取當(dāng)前的信號量許可
            int available = getState();

            //設(shè)置“獲得acquires個信號量許可之后,剩余的信號量許可數(shù)”
            int remaining = available - acquires;

            //CAS設(shè)置信號量
            if (remaining < 0 ||
                    compareAndSetState(available, remaining))
                return remaining;
        }
    }

非公平

對于非公平而言氓轰,因為它不需要判斷當(dāng)前線程是否位于CLH同步隊列列頭浸卦,所以相對而言會簡單些限嫌。

        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;
            }
        }

信號量釋放

獲取了許可,當(dāng)用完之后就需要釋放炉抒,Semaphore提供release()來釋放許可稚叹。

    public void release() {
        sync.releaseShared(1);
    }

內(nèi)部調(diào)用AQS的releaseShared(int arg):

    public final boolean releaseShared(int arg) {
        if (tryReleaseShared(arg)) {
            doReleaseShared();
            return true;
        }
        return false;
    }

releaseShared(int arg)調(diào)用Semaphore內(nèi)部類Sync的tryReleaseShared(int arg):

    protected final boolean tryReleaseShared(int releases) {
        for (;;) {
            int current = getState();
            //信號量的許可數(shù) = 當(dāng)前信號許可數(shù) + 待釋放的信號許可數(shù)
            int next = current + releases;
            if (next < current) // overflow
                throw new Error("Maximum permit count exceeded");
            //設(shè)置可獲取的信號許可數(shù)為next
            if (compareAndSetState(current, next))
                return true;
        }
    }

對于信號量的獲取釋放詳細(xì)過程扒袖,請參考如下博客:

  1. 【死磕Java并發(fā)】-----J.U.C之AQS:CLH同步隊列
  2. 【死磕Java并發(fā)】-----J.U.C之AQS:同步狀態(tài)的獲取與釋放
  3. 【死磕Java并發(fā)】-----J.U.C之AQS:阻塞和喚醒線程
  4. 【死磕Java并發(fā)】-----J.U.C之重入鎖:ReentrantLock

應(yīng)用示例

我們已停車為示例:

public class SemaphoreTest {

    static class Parking{
        //信號量
        private Semaphore semaphore;

        Parking(int count){
            semaphore = new Semaphore(count);
        }

        public void park(){
            try {
                //獲取信號量
                semaphore.acquire();
                long time = (long) (Math.random() * 10);
                System.out.println(Thread.currentThread().getName() + "進(jìn)入停車場季率,停車" + time + "秒..." );
                Thread.sleep(time);
                System.out.println(Thread.currentThread().getName() + "開出停車場...");
            } catch (InterruptedException e) {
                e.printStackTrace();
            } finally {
                semaphore.release();
            }
        }
    }

    static class Car extends Thread {
        Parking parking ;

        Car(Parking parking){
            this.parking = parking;
        }

        @Override
        public void run() {
            parking.park();     //進(jìn)入停車場
        }
    }

    public static void main(String[] args){
        Parking parking = new Parking(3);

        for(int i = 0 ; i < 5 ; i++){
            new Car(parking).start();
        }
    }
}

運(yùn)行結(jié)果如下:

Semaphore
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末鞭光,一起剝皮案震驚了整個濱河市泞遗,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌啡省,老刑警劉巖髓霞,帶你破解...
    沈念sama閱讀 207,113評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異结序,居然都是意外死亡纵潦,警方通過查閱死者的電腦和手機(jī)垃环,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,644評論 2 381
  • 文/潘曉璐 我一進(jìn)店門遂庄,熙熙樓的掌柜王于貴愁眉苦臉地迎上來劲赠,“玉大人,你說我怎么就攤上這事霹肝∷芗澹” “怎么了?”我有些...
    開封第一講書人閱讀 153,340評論 0 344
  • 文/不壞的土叔 我叫張陵苗沧,是天一觀的道長待逞。 經(jīng)常有香客問我网严,道長,這世上最難降的妖魔是什么震束? 我笑而不...
    開封第一講書人閱讀 55,449評論 1 279
  • 正文 為了忘掉前任垢村,我火速辦了婚禮,結(jié)果婚禮上宏榕,老公的妹妹穿的比我還像新娘侵佃。我一直安慰自己,他們只是感情好抚芦,可當(dāng)我...
    茶點故事閱讀 64,445評論 5 374
  • 文/花漫 我一把揭開白布叉抡。 她就那樣靜靜地躺著,像睡著了一般褥民。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上鹤盒,一...
    開封第一講書人閱讀 49,166評論 1 284
  • 那天,我揣著相機(jī)與錄音驼鞭,去河邊找鬼。 笑死译隘,一個胖子當(dāng)著我的面吹牛洛心,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播厅目,決...
    沈念sama閱讀 38,442評論 3 401
  • 文/蒼蘭香墨 我猛地睜開眼损敷,長吁一口氣:“原來是場噩夢啊……” “哼深啤!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起诱桂,我...
    開封第一講書人閱讀 37,105評論 0 261
  • 序言:老撾萬榮一對情侶失蹤呈昔,失蹤者是張志新(化名)和其女友劉穎韩肝,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體哀峻,經(jīng)...
    沈念sama閱讀 43,601評論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,066評論 2 325
  • 正文 我和宋清朗相戀三年切威,在試婚紗的時候發(fā)現(xiàn)自己被綠了丙号。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 38,161評論 1 334
  • 序言:一個原本活蹦亂跳的男人離奇死亡喳魏,死狀恐怖刺彩,靈堂內(nèi)的尸體忽然破棺而出枝恋,到底是詐尸還是另有隱情,我是刑警寧澤焚碌,帶...
    沈念sama閱讀 33,792評論 4 323
  • 正文 年R本政府宣布十电,位于F島的核電站,受9級特大地震影響朗徊,放射性物質(zhì)發(fā)生泄漏偎漫。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 39,351評論 3 307
  • 文/蒙蒙 一温亲、第九天 我趴在偏房一處隱蔽的房頂上張望杯矩。 院中可真熱鬧,春花似錦魂务、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,352評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至臭猜,卻和暖如春押蚤,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背活喊。 一陣腳步聲響...
    開封第一講書人閱讀 31,584評論 1 261
  • 我被黑心中介騙來泰國打工钾菊, 沒想到剛下飛機(jī)就差點兒被人妖公主榨干…… 1. 我叫王不留偎肃,地道東北人。 一個月前我還...
    沈念sama閱讀 45,618評論 2 355
  • 正文 我出身青樓滞详,卻偏偏與公主長得像紊馏,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子岸啡,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 42,916評論 2 344

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