大白話說java併發工具類-Semaphore,Exchanger

你聽___發表於2018-05-06

原創文章&經驗總結&從校招到 A 廠一路陽光一路滄桑

詳情請戳www.codercc.com

1. 控制資源併發訪問--Semaphore

Semaphore 可以理解為訊號量,用於控制資源能夠被併發訪問的執行緒數量,以保證多個執行緒能夠合理的使用特定資源。Semaphore 就相當於一個許可證,執行緒需要先通過 acquire 方法獲取該許可證,該執行緒才能繼續往下執行,否則只能在該方法出阻塞等待。當執行完業務功能後,需要通過release()方法將許可證歸還,以便其他執行緒能夠獲得許可證繼續執行。

Semaphore 可以用於做流量控制,特別是公共資源有限的應用場景,比如資料庫連線。假如有多個執行緒讀取資料後,需要將資料儲存在資料庫中,而可用的最大資料庫連線只有 10 個,這時候就需要使用 Semaphore 來控制能夠併發訪問到資料庫連線資源的執行緒個數最多隻有 10 個。在限制資源使用的應用場景下,Semaphore 是特別合適的。

下面來看下 Semaphore 的主要方法:

//獲取許可,如果無法獲取到,則阻塞等待直至能夠獲取為止
void acquire() throws InterruptedException
//同acquire方法功能基本一樣,只不過該方法可以一次獲取多個許可
void acquire(int permits) throws InterruptedException
//釋放許可
void release()
//釋放指定個數的許可
void release(int permits)
//嘗試獲取許可,如果能夠獲取成功則立即返回true,否則,則返回false
boolean tryAcquire()
//與tryAcquire方法一致,只不過這裡可以指定獲取多個許可
boolean tryAcquire(int permits)
//嘗試獲取許可,如果能夠立即獲取到或者在指定時間內能夠獲取到,則返回true,否則返回false
boolean tryAcquire(long timeout, TimeUnit unit) throws InterruptedException
//與上一個方法一致,只不過這裡能夠獲取多個許可
複製程式碼

boolean tryAcquire(int permits, long timeout, TimeUnit unit)

//返回當前可用的許可證個數
int availablePermits()
//返回正在等待獲取許可證的執行緒數
int getQueueLength()
//是否有執行緒正在等待獲取許可證
boolean hasQueuedThreads()
//獲取所有正在等待許可的執行緒集合
Collection<Thread> getQueuedThreads()
複製程式碼

另外,在 Semaphore 的構造方法中還支援指定是夠具有公平性,預設的是非公平性,這樣也是為了保證吞吐量。

一個例子

下面用一個簡單的例子來說明 Semaphore 的具體使用。我們來模擬這樣一樣場景。有一天,班主任需要班上 10 個同學到講臺上來填寫一個表格,但是老師只准備了 5 支筆,因此,只能保證同時只有 5 個同學能夠拿到筆並填寫表格,沒有獲取到筆的同學只能夠等前面的同學用完之後,才能拿到筆去填寫表格。該示例程式碼如下:

public class SemaphoreDemo {
//表示老師只有10支筆
private static Semaphore semaphore = new Semaphore(5);

public static void main(String[] args) {

    //表示50個學生
    ExecutorService service = Executors.newFixedThreadPool(10);
    for (int i = 0; i &lt; 10; i++) {
        service.execute(() -&gt; {
            try {
                System.out.println(Thread.currentThread().getName() + "  同學準備獲取筆......");
                semaphore.acquire();
                System.out.println(Thread.currentThread().getName() + "  同學獲取到筆");
                System.out.println(Thread.currentThread().getName() + "  填寫表格ing.....");
                TimeUnit.SECONDS.sleep(3);
                semaphore.release();
                System.out.println(Thread.currentThread().getName() + "  填寫完表格,歸還了筆!!!!!!");
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        });
    }
    service.shutdown();
}
複製程式碼

} 輸出結果:

pool-1-thread-1 同學準備獲取筆...... pool-1-thread-1 同學獲取到筆 pool-1-thread-1 填寫表格ing..... pool-1-thread-2 同學準備獲取筆...... pool-1-thread-2 同學獲取到筆 pool-1-thread-2 填寫表格ing..... pool-1-thread-3 同學準備獲取筆...... pool-1-thread-4 同學準備獲取筆...... pool-1-thread-3 同學獲取到筆 pool-1-thread-4 同學獲取到筆 pool-1-thread-4 填寫表格ing..... pool-1-thread-3 填寫表格ing..... pool-1-thread-5 同學準備獲取筆...... pool-1-thread-5 同學獲取到筆 pool-1-thread-5 填寫表格ing.....

pool-1-thread-6 同學準備獲取筆...... pool-1-thread-7 同學準備獲取筆...... pool-1-thread-8 同學準備獲取筆...... pool-1-thread-9 同學準備獲取筆...... pool-1-thread-10 同學準備獲取筆......

複製程式碼

pool-1-thread-4 填寫完表格,歸還了筆!!!!!! pool-1-thread-9 同學獲取到筆 pool-1-thread-9 填寫表格ing..... pool-1-thread-5 填寫完表格,歸還了筆!!!!!! pool-1-thread-7 同學獲取到筆 pool-1-thread-7 填寫表格ing..... pool-1-thread-8 同學獲取到筆 pool-1-thread-8 填寫表格ing..... pool-1-thread-1 填寫完表格,歸還了筆!!!!!! pool-1-thread-6 同學獲取到筆 pool-1-thread-6 填寫表格ing..... pool-1-thread-3 填寫完表格,歸還了筆!!!!!! pool-1-thread-2 填寫完表格,歸還了筆!!!!!! pool-1-thread-10 同學獲取到筆 pool-1-thread-10 填寫表格ing..... pool-1-thread-7 填寫完表格,歸還了筆!!!!!! pool-1-thread-9 填寫完表格,歸還了筆!!!!!! pool-1-thread-8 填寫完表格,歸還了筆!!!!!! pool-1-thread-6 填寫完表格,歸還了筆!!!!!! pool-1-thread-10 填寫完表格,歸還了筆!!!!!! 複製程式碼

根據輸出結果進行分析,Semaphore 允許的最大許可數為 5,也就是允許的最大併發執行的執行緒個數為 5,可以看出,前 5 個執行緒(前 5 個學生)先獲取到筆,然後填寫表格,而 6-10 這 5 個執行緒,由於獲取不到許可,只能阻塞等待。當執行緒pool-1-thread-4釋放了許可之後,pool-1-thread-9就可以獲取到許可,繼續往下執行。對其他執行緒的執行過程,也是同樣的道理。從這個例子就可以看出,Semaphore 用來做特殊資源的併發訪問控制是相當合適的,如果有業務場景需要進行流量控制,可以優先考慮 Semaphore。

2.執行緒間交換資料的工具--Exchanger

Exchanger 是一個用於執行緒間協作的工具類,用於兩個執行緒間能夠交換。它提供了一個交換的同步點,在這個同步點兩個執行緒能夠交換資料。具體交換資料是通過 exchange 方法來實現的,如果一個執行緒先執行 exchange 方法,那麼它會同步等待另一個執行緒也執行 exchange 方法,這個時候兩個執行緒就都達到了同步點,兩個執行緒就可以交換資料。

Exchanger 除了一個無參的構造方法外,主要方法也很簡單:

//當一個執行緒執行該方法的時候,會等待另一個執行緒也執行該方法,因此兩個執行緒就都達到了同步點
//將資料交換給另一個執行緒,同時返回獲取的資料
V exchange(V x) throws InterruptedException
//同上一個方法功能基本一樣,只不過這個方法同步等待的時候,增加了超時時間
V exchange(V x, long timeout, TimeUnit unit)
throws InterruptedException, TimeoutException
複製程式碼

一個例子

Exchanger 理解起來很容易,這裡用一個簡單的例子來看下它的具體使用。我們來模擬這樣一個情景,在青春洋溢的中學時代,下課期間,男生經常會給走廊裡為自己喜歡的女孩子送情書,相信大家都做過這樣的事情吧 :)。男孩會先到女孩教室門口,然後等女孩出來,教室那裡就是一個同步點,然後彼此交換信物,也就是彼此交換了資料。現在,就來模擬這個情景。

public class ExchangerDemo {
    private static Exchanger<String> exchanger = new Exchanger();
public static void main(String[] args) {

    //代表男生和女生
    ExecutorService service = Executors.newFixedThreadPool(2);

    service.execute(() -&gt; {
        try {
            //男生對女生說的話
            String girl = exchanger.exchange("我其實暗戀你很久了......");
            System.out.println("女孩兒說:" + girl);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    });
    service.execute(() -&gt; {
        try {
            System.out.println("女生慢慢的從教室你走出來......");
            TimeUnit.SECONDS.sleep(3);
            //男生對女生說的話
            String boy = exchanger.exchange("我也很喜歡你......");
            System.out.println("男孩兒說:" + boy);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    });

}
複製程式碼

}

輸出結果:

複製程式碼

女生慢慢的從教室你走出來...... 男孩兒說:我其實暗戀你很久了...... 女孩兒說:我也很喜歡你...... 複製程式碼

這個例子很簡單,也很能說明 Exchanger 的基本使用。當兩個執行緒都到達呼叫 exchange 方法的同步點的時候,兩個執行緒就能交換彼此的資料。

相關文章