併發容器J.U.C -- AQS同步元件(二)

eluanshi12發表於2018-12-26

CountDownLatch、Semaphore、CyclicBarrier、ReentrantLock、Condition、FutureTask

CountDownLatch

**計數器向下減的閉鎖 **
同步阻塞類,完成阻塞當前執行緒的功能,給定了一個計數器,原子操作,計數器不能重置。

1.通過一個計數來保證執行緒是否需要被阻塞。實現一個或多個執行緒等待其他執行緒執行的場景。
CountDownLatch
2.程式需要等待某個條件完成後,才能進行後面的操作(如父任務等待所有子任務都完成的時候,再繼續往下進行)。

我們定義一個CountDownLatch,通過給定的計數器為其初始化,該計數器是原子性操作,保證同時只有一個執行緒去操作該計數器。呼叫該類await方法的執行緒會一直處於阻塞狀態。其他執行緒呼叫countDown方法(每次使計數器-1),當計數器變為0的時候,所有等待的執行緒才會繼續執行。

final CountDownLatch countDownLatch = new CountDownLatch(threadCount);
for (int i = 0; i < threadCount; i++) {
    final int threadNum = i;
    exec.execute(() -> {
        try {
            test(threadNum);  //需要被等待的執行緒執行的方法
        } catch (Exception e) {
            log.error("exception", e);
        } finally {
            countDownLatch.countDown();
        }
    });
}
countDownLatch.await();

3.多個執行緒完成一個任務,但是這個任務只想給它一個指定的時間,超過這個時間(計數器還未清零)就不繼續等待了,完成多少算多少。(並不是第一時間毀掉所有執行緒,而是先讓正在執行的執行緒執行完)。countDownLatch.await(等待時間長度,時間單位 );

使用場景

查詢需要等待某個條件完成後才能繼續執行後續操作(Ex:平行計算)拆分任務

Semaphore 訊號量

監控併發數
保證同一時間的請求量(併發訪問控制執行緒的數目),達到上限會阻塞

訊號量在作業系統中是很重要的概念,Java併發庫裡的Semaphore就可以很輕鬆的完成類似作業系統訊號量的控制。Semaphore可以很容易控制系統中某個資源被同時訪問的執行緒個數。
在資料結構中我們學過連結串列,連結串列正常是可以儲存無限個節點的,而Semaphore可以實現有限大小的連結串列

使用場景

  1. 僅能提供有限訪問的資源。比如資料庫的最大連線數20,而上層的併發數遠遠大於20,若不做限制,可導致併發異常(無法獲取連線)。當Semaphore設定為1時,和單執行緒很相似。
  2. 併發很高,想要超過允許的併發數之後就拋棄.
/**
 * 1、普通呼叫
 */
try {
     semaphore.acquire(); // 獲取一個許可
     test();//需要併發控制的內容
     semaphore.release(); // 釋放一個許可
} catch (Exception e) {
     log.error("exception", e);
}

/**
 * 2、acquire(n),release(n)
 * 同時獲取多個許可,同時釋放多個許可
 */
 try {
     semaphore.acquire(2);
     test();
     semaphore.release(2);
} catch (Exception e) {
     log.error("exception", e);
}

//tryAcquire()
//tryAcquire(int permits)//permits嘗試獲取許可的次數
//tryAcquire(long timeout, TimeUnit unit);
//tryAcquire(int permits,long timeout, TimeUnit unit)
/*
 * 3、tryAcquire())//嘗試獲取一個許可
 * 嘗試獲取許可,獲取不到不執行
 */
 try {
     if (semaphore.tryAcquire()) {
        test(threadNum);
        semaphore.release();
     }
 } catch (Exception e) {
     log.error("exception", e);
}
/*
 * 4、嘗試獲取許可的時候等待一段時間,獲取不到不執行
 * 引數1:等待時間長度  引數2:等待時間單位
 */
try {
     if (semaphore.tryAcquire(5000, TimeUnit.MILLISECONDS)) {
        test(threadNum);
        semaphore.release(); 
     }
} catch (Exception e) {
     log.error("exception", e);
}

CyclicBarrier

同步輔助類,執行一組執行緒等待到一個公共的屏障點,實現多個執行緒相互等待,所有執行緒都準備就緒後才繼續執行,通過計數器實現的.

當某個執行緒呼叫了await()後,就會進入awaiting等待狀態,並將計數器-1,直到所有的執行緒呼叫await()使計數器為0,執行緒再同時繼續執行。

由於計數器釋放之後可以重用(reset方法),所以稱之為迴圈屏障
CyclicBarrier

使用場景

多執行緒計算資料,最後合併計算結果。
如Excel儲存使用者的銀行流水,每頁儲存了一個使用者近一年的每筆銀行流水,現統計使用者的日均銀行流水,多執行緒處理每一頁裡的銀行流水。都執行完以後得到每一頁的日均銀行流水。之後通過CyclicBarrier 的action,利用這些執行緒的計算結果,計算出整個Excel的日均流水。

//公共執行緒迴圈呼叫方法
private static CyclicBarrier barrier = new CyclicBarrier(5);

public static void main(String[] args) throws Exception {
    ExecutorService executor = Executors.newCachedThreadPool();
    for (int i = 0; i < 10; i++) {
        final int threadNum = i;
        Thread.sleep(1000);
        executor.execute(() -> {
            try {
                race(threadNum);
            } catch (Exception e) {
                log.error("exception", e);
            }
        });
    }
    executor.shutdown();
}

//使用方法1:每個執行緒都持續等待
private static void race(int threadNum) throws Exception {
    Thread.sleep(1000);
    log.info("{} is ready", threadNum);
    barrier.await();
    log.info("{} continue", threadNum);
}

//使用方法2:每個執行緒只等待一段時間
private static void race(int threadNum) throws Exception {
    Thread.sleep(1000);
    try {
        barrier.await(2000, TimeUnit.MILLISECONDS);
    } catch (InterruptedException | BrokenBarrierException | TimeoutException e) {
        log.warn("BarrierException", e);
    }
}

//使用方法3:在初始化的時候設定runnable,當執行緒達到屏障時優先執行runnable
private static CyclicBarrier barrier = new CyclicBarrier(5, () -> {
    log.info("callback is running");
});

CyclicBarrier與CountDownLatch的比較

CyclicBarrier CountDownLatch
可重複用 reset() 只能使用一次
多個執行緒相互等待(內部關係) 一個或n個執行緒等待其他執行緒的關係

CyclicBarrier提供方法獲取阻塞執行緒的個數,知道阻塞的執行緒是否中斷

相關文章