1. 程式人生 > >業精於勤荒於嬉 行成於思毀於隨

業精於勤荒於嬉 行成於思毀於隨

Semaphore訊號量是用來控制同時訪問特定資源的執行緒數量,它通過協調各個執行緒,以保證合理的使用公共資源。

舉個不適合吃飯時候看的例子:比如現在有五個茅坑,有50個人要蹲坑,那麼最先搶到的五個人先蹲,剩下45個人排對等著,等有人先蹲完出來了,告訴排在第一個的人

開始前建議先去看看我之前AQS的文章,以及很多共享鎖的程式碼在之前CountDownLatch文章裡介紹過了。

看看例子:

public class SemaphoreTrain {

    static class Worker extends Thread {
        private int n;
        private Semaphore semaphore;

        public Worker(int n, Semaphore semaphore) {
            this.n = n;
            this.semaphore = semaphore;
        }

        @Override
        public void run() {
            try {
                semaphore.acquire();
                System.out.println("Worker num " + n + " use machine");
                Thread.sleep(2000);
                System.out.println("Worker num " + n + " stop use");
                semaphore.release();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }

    public static void main(String[] args) {
        int worker = 6;    //工人數
        int machine = 4;  //機器數
        Semaphore semaphore = new Semaphore(machine);
        for (int i = 0; i < worker; i++) {
            new Worker(i, semaphore).start();
        }
    }
}
Worker num 1 use machine
Worker num 3 use machine
Worker num 2 use machine
Worker num 0 use machine
Worker num 0 stop use
Worker num 1 stop use
Worker num 3 stop use
Worker num 2 stop use
Worker num 4 use machine
Worker num 5 use machine
Worker num 4 stop use
Worker num 5 stop use

只允許最多四個執行緒同時執行,其它的等待執行執行緒release退出。
來看看原始碼實現:

本質上於CountDownLatch一樣屬於共享鎖,有非公平與公平兩種模式。

    private final Sync sync;

    abstract static class Sync extends AbstractQueuedSynchronizer {
        private static final long serialVersionUID = 1192457210091910933L;

        Sync(int permits) {
            setState(permits);
        }

        final int getPermits() {
            return getState();
        }

        // 以上面的例子來說明:非公平鎖的獲取鎖邏輯是先計算出空閒機器數remaining,若其小於0直接返回remaining
        //若大於等於0,CAS賦值state,返回remaining
        //根據AQS共享鎖的規則,返回值小於0則代表無法獲取到鎖需要入同步佇列等待。若大於等於0,執行緒不會被阻塞
        final int nonfairTryAcquireShared(int acquires) {
            for (;;) { // 為什麼要將操作放在無限for迴圈裡?迴圈CAS + volatile = 執行緒安全
                int available = getState();
                int remaining = available - acquires;
                if (remaining < 0 ||
                    compareAndSetState(available, remaining))
                    return remaining;
            }
        }

        // 公平與非公平模式的嘗試釋放鎖的操作相同,即這裡的tryReleaseShared
        // 還以上面例子說明:一個工人用完機器了,他需要做的就是歸還機器,就是CAS更改state值,成功就返回true
        protected final boolean tryReleaseShared(int releases) {
            for (;;) { // 這裡為什麼也放在無限for迴圈裡?迴圈CAS + volatile = 執行緒安全
                int current = getState();
                int next = current + releases;
                if (next < current) // overflow
                    throw new Error("Maximum permit count exceeded");
                if (compareAndSetState(current, next))
                    return true;
            }
        }

        final void reducePermits(int reductions) {
            for (;;) {
                int current = getState();
                int next = current - reductions;
                if (next > current) // underflow
                    throw new Error("Permit count underflow");
                if (compareAndSetState(current, next))
                    return;
            }
        }

        final int drainPermits() {
            for (;;) {
                int current = getState();
                if (current == 0 || compareAndSetState(current, 0))
                    return current;
            }
        }
    }

這裡說點我對執行緒安全的思考:同步狀態state在AQS中是volatile修飾的,確保了可見性,迴圈CAS確保了符合操作的原子性,二者結合保證了state操作的執行緒安全性。這就是上面程式碼中兩個問題的答案,以nonfairTryAcquireShared為例,一個執行緒通過getState得到了state的最新值,計算出了remaining大於0,於是CAS賦值,那麼在這些個操作過程中,state很可能已經被改變,CAS會失敗,如何處理?迴圈再來一次唄,再次獲取state最新值進行計算不就可以了。Doug Lea大神在設計JUC框架時採用了這種非鎖式的不排他的方法來確保執行緒安全。


非公平鎖

    static final class NonfairSync extends Sync {
        private static final long serialVersionUID = -2694183684443567898L;

        NonfairSync(int permits) {
            super(permits);
        }

        // 實現AQS共享鎖的tryAcquireShared方法,內部呼叫了nonfairTryAcquireShared
        protected int tryAcquireShared(int acquires) {
            return nonfairTryAcquireShared(acquires);
        }
    }

公平鎖

    static final class FairSync extends Sync {
        private static final long serialVersionUID = 2014338818796000944L;

        FairSync(int permits) {
            super(permits);
        }

        // Semaphore公平模式獲取共享鎖邏輯
        protected int tryAcquireShared(int acquires) {
            for (;;) {
                // 之前文章說過公平就是不允許插隊,實現邏輯就在hasQueuedPredecessors裡。
                // 該方法當同步佇列裡有其它等待更久的執行緒就返回true,代表你不被允許執行乖乖後面排對去
                if (hasQueuedPredecessors())
                    return -1; // AQS共享鎖規定tryAcquireShared返回值<0則代表獲取鎖失敗,構成節點放到佇列
                int available = getState();
                int remaining = available - acquires;
                if (remaining < 0 ||
                    compareAndSetState(available, remaining))
                    return remaining;
            }
        }
    }


建構函式

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

    // 布林值選擇模式
    public Semaphore(int permits, boolean fair) {
        sync = fair ? new FairSync(permits) : new NonfairSync(permits);
    }

acquire

獲取鎖操作:

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

//-----------------------------方法在AQS中
    public final void acquireSharedInterruptibly(int arg)
            throws InterruptedException {
        if (Thread.interrupted())
            throw new InterruptedException(); // 直接拋異常
//小於零沒有獲得許可,構造節點加入佇列中等待
        if (tryAcquireShared(arg) < 0)  
            doAcquireSharedInterruptibly(arg);
    }

    private void doAcquireSharedInterruptibly(int arg)
        throws InterruptedException {
        final Node node = addWaiter(Node.SHARED); // 構造節點加入到佇列。節點為共享節點代表共享鎖
        boolean failed = true;
        try {
            for (;;) {
                final Node p = node.predecessor();
                if (p == head) {
                    int r = tryAcquireShared(arg); // r表示剩下的空餘位
                    if (r >= 0) { 
                        setHeadAndPropagate(node, r); // 將當前獲取鎖的節點設定為頭節點,根據情況喚醒後繼執行緒
                        p.next = null; // help GC
                        failed = false;
                        return;
                    }
                }
//這裡shouldParkAfterFailedAcquire會將前面節點的狀態改為Signal
//對於等待佇列中節點的waitStatus初始為0,之後由後加入的節點改為SIGNAL
//SIGNAL這個狀態它表明你有後繼節點,release時喚醒它
                if (shouldParkAfterFailedAcquire(p, node) &&
                    parkAndCheckInterrupt())
                    throw new InterruptedException(); //直接拋異常
            }
        } finally {
            if (failed)
                cancelAcquire(node);
        }
    }

setHeadAndPropagate

    // 該方法只在doAcquireShared中呼叫。當同步佇列中head.next節點被喚醒並獲取了共享鎖,它會呼叫該方法,
    //目的1,將該節點設為新的頭節點。
    //2,嘗試喚醒後繼節點。我麼來分析分析if的這些判斷條件:若引數propagate>0(表明仍有空位可爭)且有後繼的共享節點,則喚醒後繼節點
    //or 無空缺被佔滿了,propagate <=0,但h的狀態或是新的頭節點node的狀態waitStatus<0,表明有可能有後繼節點(因為PROPAGATE也<0),
    //這種情況下我們也去呼叫喚醒操作。哪怕是h為null,表示我們無法獲取到相關資訊的情況下仍然會去嘗試喚醒node的後繼節點(若其存在的化)。
    // 可以看出這種設計是保守的,它儘可能多的去嘗試喚醒等待的執行緒,跟獨佔鎖的操作截然不同,這就造成在併發競爭下可能會發生不必要的喚醒,
    //不過這些節點總是需要被喚醒的,不如就在這裡多嘗試。

    //對於上面說的waitStatus<0情況下節點狀態為PROPAGATE的情況,該種情況下同樣無法獲知有無後繼節點
    //關於PROPAGATE:該狀態只在doReleaseShared中被設定,有執行緒釋放了共享鎖,但由於當時頭節點狀態為0,
    //無法獲知有無後繼節點,於是將頭節點狀態改為PROPAGATE),
    //那麼此時情況可能已經變了,有了後繼節點,所以我們嘗試去喚醒後繼共享節點
    private void setHeadAndPropagate(Node node, int propagate) {
        Node h = head; // Record old head for check below
        setHead(node); //將node設定成新頭節點

        if (propagate > 0 || h == null || h.waitStatus < 0 ||
            (h = head) == null || h.waitStatus < 0) {
            Node s = node.next;
            if (s == null || s.isShared())
                doReleaseShared(); // doReleaseShared方法喚醒head.next節點的執行緒
        }
    }

總之setHeadAndPropagate喚醒後繼節點的操作偏於保守,它儘可能多的去喚醒後繼的執行緒。

release

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

    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))
                        continue;            // loop to recheck cases
                    unparkSuccessor(h); // 喚醒後繼節點
                }
                // PROPAGATE這個標記只在一處用到,即這裡,該方法在同步佇列頭節點狀waitStatus=0
                //時將其設為PROPAGATE ,本身doReleaseShared是為了釋放後繼節點的,但是當頭節點狀態為0,我們不知
                //道有沒有後繼節點,所以就採用這種方式,將頭節點標記為PROPAGATE,意味著將共享鎖的釋放傳遞下去
                //並不會對之後的操作有什麼影響,之後進隊的節點獲取不到鎖就會呼叫
                //shouldParkAfterFailedAcquire,該方法會將頭節點改為SIGNAL。
                else if (ws == 0 &&
                         !compareAndSetWaitStatus(h, 0, Node.PROPAGATE))
                    continue;                // loop on failed CAS
            }
            if (h == head)               // loop if head changed
                break;
        }
    }

總結

Semaphore維持了一個同步狀態state(大小初始化時設定,代表最大允許執行執行緒數),在acquire時將state減1,小於零加入同步佇列等待,大於零則CAS更改值。
Semaphore.release happen-before Semaphore.acquire。

Actions prior to "releasing" synchronizer methods such as Lock.unlockSemaphore.release, and CountDownLatch.countDown happen-before actions subsequent to a successful "acquiring" method such as Lock.lockSemaphore.acquireCondition.await, and CountDownLatch.await on the same synchronizer object in another thread.

相關推薦

業精於勤-

最近恰逢日程都集中到了一起, 心思有點混亂, 感情有點彌散, 夢裡也竟然思考了很多。   這是正常的吧, 總會有比較的, 人又是貪婪的, 你不能期望你擁有的什麼都是最好的, 要適時降低自己的期望, 人生才會過得幸福。   其實這次的選擇並不差, 只是提前感受到了一

業精於勤

Semaphore訊號量是用來控制同時訪問特定資源的執行緒數量,它通過協調各個執行緒,以保證合理的使用公共資源。 舉個不適合吃飯時候看的例子:比如現在有五個茅坑,有50個人要蹲坑,那麼最先搶到的五個人先蹲,剩下45個人排對等著,等有人先蹲完出來了,告訴排在第一個的人 開始

【Kevin's Blog】業精於勤

KaliTools Kali Linux遵循Debian開發標準重建,經過之前的BackTrack重寫,擁有其良好血統。預裝了超過300個滲透測試軟體,像資訊蒐集;漏洞分析;無線攻擊;嗅探欺騙;密碼攻擊;維持訪問;漏洞利用;逆向工程;壓力測試;取證工具類等等,比如N

業精於勤

勤基本能做到了,但是要更勤! 思還沒有做到,現在只做到大量的獲取知識,思考方面做的比較少,需要注意啊!!! 什麼叫做思考? 就是同一件事情,你思考如何處理,別人思考如何處理,再做對比,分析過程和結果,學習別人思考問題的角度和方法,以便提升自我智慧,更好地解決問題。 教育的目

業精於勤

public class Main { public static final String CONTAINER_KEY = "dubbo.container"; public static final String SHUTDOWN_HOO

業精於勤

Bootstrap本身帶有分頁元件,以此為基礎延伸一個 JavaScript類,以便更好使用。效果圖如下: 使用上必須先在 html檔中部署 Bootstrap分頁元件,並命名為 #nvaPageUL,如下: <nav aria-label="Page navigation">

沉浸此,體系,求自由

         分片是分散式儲存的突出特點。 必要性          如果Redis叢集的每個資料庫都儲存叢集中的所有資料,那麼叢集的總資料儲存量受限於可用儲存記憶體最小的資料庫節點,形成木桶效應。由於Redis中的所有資料都基於記憶體儲存,這一問題就尤為突出了,尤其是

超長顯示省略號...,相容各瀏覽器,適用

超長顯示省略號是一個很困擾的事情,各個瀏覽器的對css的解析不同,比如 1. IE可以使用overflow:hidden;white-space:nowrap;text-overflow:ellipsis, 2. Opera中也有相關的支援屬性text-overflow:-

iOS動態高佈局(區別傳統計算字數)

iOS動態杭高一般都採用sizeWithFont: constrainedToSize:這種方式來計算出高度 然後返回行高;最近我發現一種新的方式,就是不需要計算的,動態佈局;只需要在tableView裡面,分三個步驟來寫:1:給 estimateRowHeight 賦值,這

Spring Boot 中直接操作 hbase 修改賬戶餘額,實現級鎖(類似版本號控制)

應用場景近期開發中遇到 直接修改hbase資料 ,用Phoenix 查詢出來的資料  型別不一致的 問題。因修改的是使用者的賬戶餘額,涉及到錢的問題都不是小問題。初次想法使用tephra事務,但官網說目前還是 Beta版本的,感興趣的可以研究研究。所以考慮直接操作hbase資

sql合併多列一個字串,多一個字串

以這個表為例: sql1: select GROUP_CONCAT(prov_code SEPARATOR ',') AS prov_code from tb_prov_code 可以看出,順序就是表裡面的順序。這裡是按照逗號隔開。 sql2: 想要拼接同一行 select

數字中國·渝論壇順利召開, 鷹雲智慧攜手阿里雲助力傳統商圈構築出數字化新場景

做新零售,成都已經成為了各大商業巨頭必爭之地。 當下,成都作為北京、上海、深圳之後,排名第四的零售之城,僅僅在2018年上半年成都實現社會消費品零售總額3326.4億元,在副省級城市零售總額指標增速排位中,成都消費潛力增速升至第1位,其中最亮眼的表現就是在新零售的佈局上。 因此,為了更好的促進新零售與購物

3星|《文明的崩塌:公元前1177年的地中海世界》:青銅時代全球化國際化的地中海地區,最終天災人禍

文明的崩塌:公元前1177年的地中海世界 作者是考古學家。書中綜述考古學的一些發現和學說:三千年前的青銅時代晚期,地中海各國之間出現了頻道的貿易往來,有點像國際化大勢下的當今世界,但是在300年的繁榮之後,一些天災人禍讓這個國際化體系崩潰了,直到數百年後才逐步恢復。 作者坦言這個觀點還有爭議,他也不是

《精益創業》- 天下大事必作細,天下難事必作

《道德經》和《精益創業》的暗合可以顯示古今中外的人類智慧多麼相通。不過,與老子惜字如金的風格不同,Eric Ries從實際操作的角度寫出了一本300頁厚的書,對如何“作於細”和“作於易”給出了系統化的建議。   背景:   Eric Ries是一個矽谷的程式開發者,曾參

不能繼承QObject的類就一定不能使用信號槽?(用一個代理類進行發射就了)

-c 問題 ges object 編譯過程 報錯 第三方庫 nal 解決 首先不能繼承QObject的情況在開發中遇到得並不多,筆者在一年多的Qt項目開發中只遇到兩三次。而且都是因為引進了第三方庫導致編譯過程中報錯。 要想解決這個問題其實不難,因為筆者遇到的問題都是想定義

ESP8266 是一個完整且自成體系的 WiFi 網絡解決方案,能夠獨立運,也可以作為從機搭載其他主機 MCU 運

處理 天線 -1 系統資源 pwm adc 高度 能夠 能力 ESP8266EX 在搭載應用並作為設備中唯一的應用處理器時,能夠直接從外接閃存中啟動。內置的高速緩沖存儲器有利於提高系統性能,並減少內存需求。另外一種情況是,ESP8266EX 負責無線上網接入承擔 WiF

《Elixir in Action》書評及作者問答錄(作者 Sergio De Simone ,譯者 邵華 發布 2015年9月29日)

服務器 編程思想 href 地產 完全 負載 server 後臺 tsa 《Elixir in Action》是由Manning所出版的一本新書,本書為讀者介紹了Elixir這門語言以及Erlang虛擬機,同時也討論了與並發編程、容錯以及與高可用性相關的話題。InfoQ有幸

木秀林,風必摧之;高於人,眾必之?

不同 三國 行高 問題 pst 自己 www. 微軟雅黑 朋友   前段時間在一本小說裏面看到這句話,心裏感觸頗深,特地在此寫下感想,簡單分析下含義。 這句話原出自三國魏人李康的《運命論》。看到這句話,我想跟多人會為秀木和高人感到惋惜,憐憫之情油然而生。但是在我們感嘆他們的

全網最詳細的IDEA、Eclipse和MyEclipse之間Java web項目發布到Tomcat上運成功的對比事宜【博主強烈推薦】【適合普通的還是Maven方式創建的】(圖文詳解)

led 交流 之間 精神 推薦 enter style images java web     不多說,直接上幹貨!   IDEA 全網最詳細的IDEA裏如何正確新建

css設定固定高度多超出變省略號,適用手機端

基本設定:  display: -webkit-box;/** 物件作為伸縮盒子模型顯示 **/ word-break: break-all; text-overflow: ellipsis; -webkit-box-orient: vertical