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。線程
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方法的同步點的時候,兩個線程就能交換彼此的數據。