1. 程式人生 > >JAVA的幾個同步輔助類

JAVA的幾個同步輔助類

Java為我們提供了一些同步輔助類,利用這些輔助類我們可以在多執行緒程式設計中,靈活地把握執行緒的狀態。

CountDownLatch

CountDownLatch一個同步輔助類,在完成一組正在其他執行緒中執行的操作之前,它允許一個或多個執行緒一直等待。

再CountDownLatch中兩個比較關鍵的方法:

public void await() throws InterruptedException;
public void countDown();

CountDownLatch是一個計數器,它的構造方法中需要設定一個數值,用來設定計數的次數。每次呼叫countDown()方法之後,這個計數器都會減去1

,CountDownLatch會一直阻塞著呼叫await()方法的執行緒,直到計數器的值變為0

設想有這樣一個功能需要Thread1、Thread2、Thread3、Thread4四條執行緒分別統計C、D、E、F四個盤的大小,所有執行緒都統計完畢交給主執行緒去做彙總,利用CountDownLatch來完成就非常輕鬆。

public class CountDownLatchTest {

    private static CountDownLatch count = new CountDownLatch(4);
    private static ExecutorService service = Executors.newFixedThreadPool(6
); public static void main(String args[]) throws InterruptedException { for (int i = 0; i < 4; i++) { service.execute(() -> { // 模擬任務耗時 try { int timer = new Random().nextInt(5); TimeUnit.SECONDS.sleep(timer); System.out
.printf("%s時完成磁碟的統計任務,耗費%d秒.\n", new Date().toString(), timer); // 任務完成之後,計數器減一 count.countDown(); } catch (InterruptedException e) { e.printStackTrace(); } }); } // 主執行緒一直被阻塞,知道count的計數器被設定為0 count.await(); System.out.printf("%s時全部任務都完成,執行合併計算.\n", new Date().toString()); service.shutdown(); } }

CyclicBarrier

Barrier在英語中是屏障的意思,這個同步工具會阻塞呼叫的執行緒,直到條件滿足時,阻塞的執行緒同時被開啟。

public int await() throws InterruptedException, BrokenBarrierException

CyclicBarrier初始化的時候,設定一個屏障數。執行緒呼叫await()方法的時候,這個執行緒就會被阻塞,當呼叫await()的執行緒數量到達屏障數的時候,主執行緒就會取消所有被阻塞執行緒的狀態。

CyclicBarrier的構造方法中,還可以設定一個barrierAction

在所有的屏障都到達之後,會啟動一個執行緒來執行這裡面的程式碼。這裡舉一個例子:百米賽跑的運動員起跑前需要準備,所有選手準備完畢之後,才可以同時起跑。

public class CyclicBarrierTest {

    private static CyclicBarrier cyclicBarrier = new CyclicBarrier(8);
    private static ExecutorService service = Executors.newFixedThreadPool(50);

    public static void main(String args[]) {
        for (int i = 1; i < 9; i++) {
            service.execute(new Thread(new Runner(i, cyclicBarrier)));
        }
        service.shutdown();
    }
}
// 運動員類
public class Runner implements Runnable {

    private int number;
    private CyclicBarrier cyclicBarrier;

    public Runner(int number, CyclicBarrier cyclicBarrier) {
        this.number = number;
        this.cyclicBarrier = cyclicBarrier;
    }

    @Override
    public void run() {
        try {
            int timer = new Random().nextInt(5);
            TimeUnit.SECONDS.sleep(timer);
            System.out.printf("%d號選手準備完畢,準備時間%d\n", number, timer);
            cyclicBarrier.await();
            System.out.printf("%d號選手於%s時起跑!\n", number, new Date().toString());
        } catch (InterruptedException | BrokenBarrierException e) {
            e.printStackTrace();
        }
    }
}

輸出:

1號選手準備完畢,準備時間0
4號選手準備完畢,準備時間0
5號選手準備完畢,準備時間1
8號選手準備完畢,準備時間1
3號選手準備完畢,準備時間2
2號選手準備完畢,準備時間3
7號選手準備完畢,準備時間3
6號選手準備完畢,準備時間3
7號選手於Sun Mar 27 21:19:00 CST 2016時起跑!
2號選手於Sun Mar 27 21:19:00 CST 2016時起跑!
5號選手於Sun Mar 27 21:19:00 CST 2016時起跑!
6號選手於Sun Mar 27 21:19:00 CST 2016時起跑!
3號選手於Sun Mar 27 21:19:00 CST 2016時起跑!
8號選手於Sun Mar 27 21:19:00 CST 2016時起跑!
4號選手於Sun Mar 27 21:19:00 CST 2016時起跑!
1號選手於Sun Mar 27 21:19:00 CST 2016時起跑!

相比CountDownLatchCyclicBarrier是可以被迴圈使用的,而且遇到執行緒中斷等情況時,還可以利用reset()方法,重置計數器,從這些方面來說,CyclicBarrier會比CountDownLatch更加靈活一些。

Semaphore

Semaphore被用於控制特定資源在同一個時間被訪問的個數。類似連線池的概念,保證資源可以被合理的使用。

Semaphore的幾個重要方法:

// 獲取資源
public void acquire() throws InterruptedException
// 釋放資源
public void release()

Semaphore的構造方法可以設定一個int值來設定一個計數器,用於表示資源同時可以被多少外部環境使用。每使用一次acquire(),計數器都會去減去一,而每次呼叫release()計數器則會增加一。當計數器的值為0的時候,外部的環境被阻塞,直到Semaphore有空閒的資源可以被使用。

public class SemaphoreTest {

    private static Semaphore semaphore = new Semaphore(3);
    private static ExecutorService service = Executors.newFixedThreadPool(6);

    public static void main(String args[]) {

        // 執行9個任務
        for (int i = 0; i < 9; i++) {
            service.execute(() -> {
                try {
                    semaphore.acquire();
                    System.out.printf("%s時獲取資源,並呼叫.\n", new Date().toString());
                    // 執行緒掛起3秒
                    TimeUnit.SECONDS.sleep(3);
                    semaphore.release();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            });
        }
        service.shutdown();
    }
}

執行的結果就是:

Sun Mar 27 20:18:16 CST 2016時獲取資源,並呼叫.
Sun Mar 27 20:18:16 CST 2016時獲取資源,並呼叫.
Sun Mar 27 20:18:16 CST 2016時獲取資源,並呼叫.
Sun Mar 27 20:18:19 CST 2016時獲取資源,並呼叫.
Sun Mar 27 20:18:19 CST 2016時獲取資源,並呼叫.
Sun Mar 27 20:18:19 CST 2016時獲取資源,並呼叫.
Sun Mar 27 20:18:22 CST 2016時獲取資源,並呼叫.
Sun Mar 27 20:18:22 CST 2016時獲取資源,並呼叫.
Sun Mar 27 20:18:22 CST 2016時獲取資源,並呼叫.

雖然執行緒池允許6個最大執行緒數量,但是同一個時間內只用三個任務被執行。