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

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的構造方法中還支持指定是夠具備公平性,默認的是非公平性,這樣也是爲了保證吞吐量。大數據

一個例子ui

下面用一個簡單的例子來講明Semaphore的具體使用。咱們來模擬這樣同樣場景。有一天,班主任須要班上10個同窗到講臺上來填寫一個表格,可是老師只准備了5支筆,所以,只能保證同時只有5個同窗可以拿到筆並填寫表格,沒有獲取到筆的同窗只可以等前面的同窗用完以後,才能拿到筆去填寫表格。該示例代碼以下:spa

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 < 10; i++) {
            service.execute(() -> {
                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方法,這個時候兩個線程就都達到了同步點,兩個線程就能夠交換數據。code

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

//當一個線程執行該方法的時候,會等待另外一個線程也執行該方法,所以兩個線程就都達到了同步點
//將數據交換給另外一個線程,同時返回獲取的數據
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(() -> {
            try {
                //男生對女生說的話
                String girl = exchanger.exchange("我其實暗戀你好久了......");
                System.out.println("女孩兒說:" + girl);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        });
        service.execute(() -> {
            try {
                System.out.println("女生慢慢的從教室你走出來......");
                TimeUnit.SECONDS.sleep(3);
                //男生對女生說的話
                String boy = exchanger.exchange("我也很喜歡你......");
                System.out.println("男孩兒說:" + boy);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        });

    }
}

輸出結果:

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

這個例子很簡單,也很能說明Exchanger的基本使用。當兩個線程都到達調用exchange方法的同步點的時候,兩個線程就能交換彼此的數據。

相關文章
相關標籤/搜索