Java 多線程(六)- 常用同步類

ReentrantReadWriteLock

概述

嚴格來說 ReentrantReadWriteLock 是鎖,不應該在這篇文章里,但是為了篇幅,還是將它放入。 ReentrantReadWriteLock 實現 ReadWriteLock 接口:

public interface ReadWriteLock {  
    //獲得讀鎖
        Lock readLock();
        //獲得寫鎖  
        Lock writeLock();  
}   

其中寫鎖是獨占鎖,讀鎖是共享鎖。

使用場景

一個資源能夠被多個讀線程訪問,或者被一個寫線程訪問,但是不能同時存在讀寫線程。

適用于一個共享資源被大量讀取操作,而只有少量的寫操作(修改數據)。

特點
  • 公平性: 讀鎖之間沒有公平性,但是寫鎖是獨占鎖,它的公平性規則和 ReentrantLock 一樣分為 公平鎖非公平鎖(默認);
  • 重入性
    1. 讀寫鎖允許讀線程和寫線程按照請求鎖的順序重新獲取讀取鎖或者寫入鎖。當然只有寫線程釋放了鎖,讀線程才能重新獲取重入鎖;
    2. 寫線程獲取寫入鎖,可以再次獲取讀取鎖,但是讀線程獲取讀取鎖后卻不能獲取寫入鎖
    3. 讀寫鎖最多支持 65535 個重入的寫入鎖和讀取鎖。因為讀寫鎖的同步器繼承 AQS,AQS 的 state 用來表示重入的線程數,它的高 16 位用來表示寫線程重入數,低 16 位 用來表示讀線程重入數,所以最多支持 65535。
  • 鎖降級。寫線程獲取寫入鎖后可以獲取讀寫鎖,然后釋放寫入鎖,這樣就從寫入鎖變成了讀取鎖,實現鎖降級;
  • 不支持鎖升級。
  • 條件變量(Condition)
    1. 寫入鎖是獨占鎖。提供條件變量支持;
    2. 讀取鎖是共享鎖,不允許獲取條件變量,否則會拋出 UnsupportedOperationException
實例代碼
class CachedData {
    private Object data;
    private volatile boolean cacheValid;
    private ReentrantReadWriteLock rwl = new ReentrantReadWriteLock();

    public void processCachedData() {
            rwl.readLock().lock();
            if (!cacheValid) {
                    // Must release read lock before acquiring write lock
                    rwl.readLock().unlock();
                    rwl.writeLock().lock();
    
                    // Recheck state because another thread might have acquired
                    //   write lock and changed state before we did.
                    if (!cacheValid) {
                    data = ...
                    cacheValid = true;
                    }
    
                    // Downgrade by acquiring read lock before releasing write lock
                    rwl.readLock().lock();
                    rwl.writeLock().unlock(); // Unlock write, still hold read
            }

            use(data);
            rwl.readLock().unlock();
    }
}

Semaphore

概述

Semaphore(信號量)用來控制同時訪問某個特定資源的操作數量,或者同時執行某個指定操作的數量。常用來實現某種資源池,或者對容器施加邊界

Semaphore 管理者一組虛擬的許可(permit),許可的初始數量可以通過構造函數制定。具體應用中,執行操作之前首先獲得許可,并在使用以后釋放許可,如果許可不夠,線程將被掛起。如果許可初始數量為 1,則 Semaphore 被稱作計算信號量,可以用作互斥體(Mutex),并且擁有了不可重入的加鎖語義。

Semaphore 內部的 Sync 繼承自 AQS,使用了其共享模式的接口,所以 Semaphore 并不支持 Condition 條件變量。

實例代碼
class BoundHashSet<T> {
    private final Set<T> set;
        private final Semaphore sem;

        public BoundHashSet(int bound) {
                set = new HashSet<T>();
                sem = new Semaphore(bound);
        }

        public boolean add(T o) throws InterruptedException {
                sem.acquire();

                boolean wasAdded = false;
                try {
                    wasAdded = set.add(o);
                    return wasAdded;
                } finally {
                    if (!wasAdded) {
                            sem.release();
                    }
                }
        }

        public boolean remove(T o) {
                boolean wsRemoved = set.remove(o);
                if (wsRemoved) {
                    sem.release();
                }
                return wsRemoved;
        }
}    

CountDownLatch

概述

CountDownLatch 是一種靈活的閉鎖實現,閉鎖 是一種同步工具,可以延遲線程的進度直到其到達終止狀態。它的作用相當于一扇門:在閉鎖到達結束狀態之前,這扇門一直關閉,沒有任何線程通過,當到達結束狀態時,這扇門會打開允許所有線程通過。

使用場景

閉鎖(CountDownLatch)可以用來確保某些活動直到其他活動都完成了才繼續執行。比如:

  • 確保某個計算在其需要的所有資源都被初始化之后才繼續執行;
  • 確保某個服務在其依賴的所有其他服務都已經啟動后才啟動;
  • 等待知道某個操作的所有參與者都就緒再繼續執行。
主要接口

CountDownLatch 有兩類主要接口:

//供等待線程調用
public void await() throws InterruptedException
public boolean await(long timeout, TimeUnit unit)
    throws InterruptedException
    
//共通知線程調用
public void countDown()

CountDownLatch 內部的 Sync 繼承自 AQS,私用的 state 代等待線程的計數器 count。調用CountDownLatch.await 的線程會阻塞掛起,一直到計數器減到 0。其他線程調用 CountDownLatch.countDown,每次都會遞減計數器,計時器到 0 時,會喚醒所有阻塞在 await 的線程。他們的調用時序圖如下所示:

CountDownLatch 調用時序
特點

CountDownLatch 注意以下特點:

  1. 等待計數器 count 只有 CountDownLatch 初始化時才能設置,沒有其他接受改變它,所以 CountDownLatch 是一次性的同步類;
  2. 調用 await 和 countDown 的線程不可能是同一個;
  3. 內部 Sync 重載的是 AQS 共享模式接口,所以不支持 Condition。
編程實踐
// 一個CountDownLatch實例是不能重復使用的,也就是說它是一次性的,鎖一經被打開就不能再關閉使用了,如果想重復使用,請考慮使用CyclicBarrier。
public class CountDownLatchTest {

// 模擬了100米賽跑,10名選手已經準備就緒,只等裁判一聲令下。當所有人都到達終點時,比賽結束。
    public static void main(String[] args) throws InterruptedException {

        // 開始的倒數鎖
        final CountDownLatch begin = new CountDownLatch(1);

        // 結束的倒數鎖
        final CountDownLatch end = new CountDownLatch(10);

        // 十名選手
        final ExecutorService exec = Executors.newFixedThreadPool(10);

        for (int index = 0; index < 10; index++) {
            final int NO = index + 1;
            Runnable run = new Runnable() {
                public void run() {
                    try {
                        // 如果當前計數為零,則此方法立即返回。
                        // 等待
                        begin.await();
                        Thread.sleep((long) (Math.random() * 10000));
                        System.out.println("No." + NO + " arrived");
                    } catch (InterruptedException e) {
                    } finally {
                        // 每個選手到達終點時,end就減一
                        end.countDown();
                    }
                }
            };
            exec.submit(run);
        }
        System.out.println("Game Start");
        // begin減一,開始游戲
        begin.countDown();
        // 等待end變為0,即所有選手到達終點
        end.await();
        System.out.println("Game Over");
        exec.shutdown();
    }
}

CyclicBarrier

概述

CyclicBarrier 的字面意思是可循環使用(Cyclic)的柵欄(Barrier)。柵欄類似于閉鎖,它能阻塞一組線程直到某個事件發生。柵欄和閉鎖的關鍵區別在于,所有線程必須都到達柵欄位置,才能繼續執行。閉鎖用于等待事件,柵欄用于等待線程

使用場景

CyclicBarrier 在并行迭代算法中非常有用,這種算法通常將一個問題拆分成一系列相互獨立的子問題,各個子問題由不同線程計算,單個線程計算完成后調用 await 等候在柵欄處,所有線程計算完畢后都等候在柵欄時,柵欄再打開釋放所有線程。

如果某個線程的 await 超時或者被中斷,其他已經在 await 的線程會終止等待并拋出 BrokenBarrierException。

特點
  1. 可以循環使用,柵欄釋放所有線程后,重新初始化 count 計數器。
  2. 提供 reset 接口重置 count 計數器,已經 await 的線程會拋出 BrokenBarrierException。
  3. 初始化時可以傳入 Runnable 接口的實現,所有線程都到達柵欄后,在最后一個到達線程上執行這個 Runnable。
  4. 內部使用 ReentrantLock 和 Condition 控制同步,而不是用繼承 AQS 的 Sync。
編程實踐
class CyclicBarrierTest {
    static CyclicBarrier c = new CyclicBarrier(2);

    public static void main(String[] args) {
        new Thread(new Runnable() {
            @Override
            public void run() {
                try {
                    c.await();
                } catch (Exception e) {}
            
                System.out.println(1);
            }
        }).start();

        try {
            c.await();
        } catch (Exception e) {}
        
        System.out.println(2);
    }
}

內容來源

Java 并行編程實戰

https://my.oschina.net/adan1/blog/158107

http://www.cnblogs.com/liuling/archive/2013/08/21/2013-8-21-03.html

http://ifeve.com/concurrency-semaphore/

http://blog.csdn.net/lipeng_bigdata/article/details/52165426

http://www.itzhai.com/the-introduction-and-use-of-a-countdownlatch.html#read-more

http://developer.51cto.com/art/201403/432095.htm

http://www.importnew.com/15731.html

http://ifeve.com/concurrency-cyclicbarrier/

http://www.itzhai.com/the-introduction-and-use-of-cyclicbarrier.html#read-more

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 228,119評論 6 531
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 98,382評論 3 415
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 176,038評論 0 373
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 62,853評論 1 309
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,616評論 6 408
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 55,112評論 1 323
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,192評論 3 441
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,355評論 0 288
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 48,869評論 1 334
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 40,727評論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 42,928評論 1 369
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,467評論 5 358
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,165評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,570評論 0 26
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,813評論 1 282
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,585評論 3 390
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 47,892評論 2 372

推薦閱讀更多精彩內容