1. 程式人生 > 其它 >JUC 常見三大輔助類

JUC 常見三大輔助類

JUC 常見三大輔助類

CountDownLatch(減少計數)

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

用給定的計數 初始化 CountDownLatch,由於呼叫了 countDown() 方法,所以在當前計數到達零之前,await 方法會一直受阻塞;之後,會釋放所有等待的執行緒,await 的所有後續呼叫都將立即返回;這種現象只出現一次——計數無法被重置,如果需要重置計數,請考慮使用 CyclicBarrier

CountDownLatch 是一個通用同步工具,它有很多用途:將計數 1 初始化的 CountDownLatch 用作一個簡單的開/關鎖存器,或入口,在通過呼叫

countDown() 的執行緒開啟入口前,所有呼叫 await 的執行緒都一直在入口處等待;用 N 初始化的 CountDownLatch 可以使一個執行緒在 N 個執行緒完成某項操作之前一直等待,或者使其在某項操作完成 N 次之前一直等待

CountDownLatch 類可以設定一個計數器,然後通過 countDown 方法來進行減 1 的操作,計數器等於 0之前呼叫 await 方法執行緒處於等待狀態直到計數器等於0

總結:

  • CountDownLatch 主要有兩個方法,當一個或多個執行緒呼叫 await 方法時,這些執行緒會阻塞
  • 其它執行緒呼叫 countDown 方法會將計數器減 1(呼叫 countDown 方法的執行緒不會阻塞)
  • 當計數器的值變為 0 時,因 await 方法阻塞的執行緒會被喚醒,繼續執行

程式碼實戰

場景:教室有7 個同學,當所有同學陸續離開教室後值班同學才可以關門

package com.yl.assist;

import java.util.concurrent.CountDownLatch;

/**
 * CountDownLatch使用案例
 *
 * @author Y-wee
 */
public class CountDownLatchTest {

    public static void main(String[] args) {
        // 計數器(設定教室剩餘學生數量)
        CountDownLatch count = new CountDownLatch(7);

        for (int i = 1; i <= 7; i++) {
            new Thread(() -> {
                // 計數器減一(學生離開,教室剩餘學生數量減一)
                count.countDown();

                System.out.println(Thread.currentThread().getName() + "離開了教室");
            }, "student" + i).start();
        }

        try {
            // 計數器等於0(教室剩餘學生數量為0)之前main執行緒等待
            count.await();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        // 計數器等於0,main執行緒被喚醒(教室剩餘學生數量為0-教室關門)
        System.out.println("教室關門");

    }

}

CyclicBarrier(迴圈柵欄)

一個同步輔助類,它允許一組執行緒互相等待,直到到達某個公共屏障點 (common barrier point)

在涉及一組固定大小的執行緒的程式中,這些執行緒必須不時地互相等待,此時 CyclicBarrier 很有用,因為該 barrier 在釋放等待執行緒後可以重用,所以稱它為迴圈 的 barrier

CyclicBarrier 的構造方法第一個引數是目標障礙數,每次執行 CyclicBarrier 一次障礙數會加一,如果達到了目標障礙數,才會執行 cyclicBarrier.await()之後的語句,可以將 CyclicBarrier 理解為加 1 操作

程式碼實戰

場景:集齊 7 顆龍珠就可以召喚神龍

package com.yl.assist;

import java.util.concurrent.BrokenBarrierException;
import java.util.concurrent.CyclicBarrier;

/**
 * CyclicBarrierTest使用案例
 *
 * @author Y-wee
 */
public class CyclicBarrierTest {
    private final static int NUMBER = 7;

    public static void main(String[] args) {
        /*
        建立一個新的 CyclicBarrier,它將在給定數量的參與者(執行緒)處於等待狀態時啟動
        並在啟動 barrier 時執行給定的屏障操作,該操作由最後一個進入 barrier 的執行緒執行
        (集齊七顆龍珠才可以召喚神龍)
         */
        CyclicBarrier cyclicBarrier = new CyclicBarrier(NUMBER,()-> System.out.println("集齊七顆龍珠,召喚神龍成功"));

        for (int i = 1; i <= 7; i++) {
            new Thread(() -> {
                // 模擬收集龍珠
                System.out.println(Thread.currentThread().getName()+"集到了");
                try {
                    // 在所有參與者都已經在此 barrier 上呼叫 await 方法之前,將一直等待(龍珠集齊之前處於等待狀態)
                    cyclicBarrier.await();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                } catch (BrokenBarrierException e) {
                    e.printStackTrace();
                }

            }, "龍珠" + i).start();
        }

    }

}

Semaphore(訊號燈)

一個計數訊號量,從概念上講,訊號量維護了一個許可集,如有必要,在許可可用前會阻塞每一個 acquire(),然後再獲取該許可,每個 release() 釋放一個許可,從而可能釋放一個正在阻塞的獲取者;Semaphore 只對可用許可的號碼進行計數,並採取相應的行動

Semaphore 通常用於限制可以訪問某些資源(物理或邏輯的)的執行緒數目

總結:Semaphore維護了一個許可集,當執行緒未獲取到許可時處於等待狀態,直到執行緒獲取到許可

程式碼實戰

場景:搶車位,7 部汽車 3 個停車位

package com.yl.assist;

import java.util.concurrent.Semaphore;
import java.util.concurrent.TimeUnit;

/**
 * Semaphore使用案例
 *
 * @author Y-wee
 */
public class SemaphoreTest {

    public static void main(String[] args) {
        // 設定訊號量(許可集),模擬三個停車位
        Semaphore semaphore = new Semaphore(3);

        for (int i = 1; i <= 7; i++) {
            new Thread(() -> {
                try {
                    // 獲取訊號量(停車許可)
                    semaphore.acquire();

                    System.out.println(Thread.currentThread().getName() + "進入了停車位");

                    // 執行緒等待(停車3s)
                    TimeUnit.SECONDS.sleep(3);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                } finally {
                    /*
                    注意:離開停車位的輸出語句需要寫在釋放訊號量之前,不然可能造成訊號量(停車許可釋放了),
                    但是車子還沒有離開,此時,另一個執行緒(車子)獲取到了停車許可進來了
                     */
                    System.out.println(Thread.currentThread().getName() + "離開了停車位");

                    // 釋放訊號量(停車許可)
                    semaphore.release();
                }
            }, "車輛" + i).start();
        }
    }

}