同步工具類可以是任何一個對象,只要能夠根據其自身的狀態來協調線程的控制流就行,阻塞隊列就可以作爲同步工具類,java平臺類庫中還包含其他一些同步工具類,不過這裏主要分享CountDownLatch、CyclicBarrier、Semaphore和 Exchanger。
CountDownLatch
CountDownLatch允許一個或多個線程等待其他線程完成操作。它維護了一個鎖存器的計數,當計數爲0的時候,那些處於wait狀態的線程才能執行。 CountDownLatch是一種閉鎖,閉鎖相當於一扇門,在閉鎖到達結束狀態之前,門一直是關閉的,並且沒有任何線程能通過,當到達結束狀態時,門纔會打開並允許所有的線程通過。
假如有這樣一個需求:一個文本文件,有N行數據,通過多線程計算N行數據的和。可以通過CountDownLatch實現,先創建N個線程對每一行求和,最後主線程求整個文本文件的數據之和:
public class CountDownLatchExp {
private int [] nums;
public CountDownLatchExp(int lines){
nums = new int[lines];
}
public void calc (String line,int index,CountDownLatch latch){
String[] datas = line.split(",");
int total = 0;
for(String data:datas){
total += Integer.parseInt(data);
}
//每一行的和放到數組對應位置
nums[index] = total;
System.out.println(Thread.currentThread().getName()+"計算完成,結果爲"+total);
latch.countDown(); //一行計算完就將鎖存器減1
}
public static void main(String[] args) {
List<String> lines = readlines();//讀文件
int lineCount = lines.size();
CountDownLatch latch = new CountDownLatch(lineCount);
CountDownLatchExp demo = new CountDownLatchExp(lineCount);
for(int i=0;i<lineCount;i++){
final int j = i;
new Thread(new Runnable() {
@Override
public void run() {
demo.calc(lines.get(j), j,latch);
}
}).start();
}
latch.await(); //當鎖存器不爲0就一直等待
demo.sum();//對數組求和
}
}
CyclicBarrier
CyclicBarrier的字面意思是可循環的屏障。它要做的事情是,讓一組線程到達一個屏障(也可以叫同步點)時被阻 塞,直到最後一個線程到達屏障時,屏障纔會開門,所有被屏障攔截的線程纔會繼續運行。也就是讓一組線程到達 一個同步點後再一起繼續運行
CyclicBarrier提供一個更高級的構造函數 CyclicBarrier(int parties, Runnable barrierAction) ,用於在線程到達同步點時,優先執行線程barrierAction,這樣可以更加方便的處理一些複雜的業務場景, 且CyclicBarrier可用於多線程計算數據,最後合併計算結果的場景。
public class CalculateExp {
private int [] nums;
public CalculateExp(int lines){
nums = new int[lines];
}
public void calc (String line, int index, CyclicBarrier barrier){
String[] datas = line.split(",");
int total = 0;
for(String data:datas){
total += Integer.parseInt(data);
}
nums[index] = total;
System.out.println(Thread.currentThread().getName()+"計算完成,結果爲"+total);
barrier.await();
}
public static void main(String[] args) {
List<String> lines = readlines();
int lineCount = lines.size();
CalculateExp demo = new CalculateExp(lineCount);
CyclicBarrier barrier = new CyclicBarrier(lineCount, new Runnable() {
@Override
public void run() {
demo.sum();//數組求和
}
});
for(int i=0;i<lineCount;i++){
final int j = i;
new Thread(new Runnable() {
@Override
public void run() {
demo.calc(lines.get(j), j,barrier);
}
}).start();
}
}
}
CyclicBarrier和CountDownLatch的區別:
CountDownLatch:一個或者多個線程,等待其他多個線程完成某件事情之後才能執行; CyclicBarrier:多個線程互相等待,直到到達同一個同步點,再繼續一起執行。
CountDownLatch是計數器,只能使用一次,而CyclicBarrier的計數器提供reset功能 ,但使用reset的時候要 注意不要產生一直阻塞的線程。
Exchanger
Exchanger也是一種屏障,不過它是雙方的(Two-Party),用於進行線程間的數據交換。它提供一個同步點,在這 個同步點,兩個線程可以交換彼此的數據。這兩個線程通過 exchange方法交換數據,如果第一個線程先執行 exchange()方法,它會一直等待第二個線程也執行exchange方法,當兩個線程都到達同步點時,這兩個線程就可以交換數據,將本線程生產出來的數據傳遞給對方。
Exchanger 使用是很簡單,就提供了一個exchange操作和超時exchange操作,但實現原理不簡單,底層也通過了 CAS算法實現線程安全。
Exchanger交換分爲單槽和多槽,單個槽位在同一時刻只能用於兩個線程交換數據, 再有線程進來競爭就實行多槽交換,多槽交換允許多個線程可以同時進行兩兩數據交換,彼此之間不受影響 ,且交換的對象是不確定的。Exchanger可用於結果校對,流水線設計,生產者消費者數據交換,遺傳算法等。
//緩存交換
public class ExchangeCache {
private final static int MAX_COUNT = 10;
static class FillingLoop implements Runnable {
private Exchanger<ConcurrentHashMap<String,String>> exchanger;
ConcurrentHashMap<String,String> currentBuffer;
public FillingLoop(Exchanger<ConcurrentHashMap<String,String>>
exchanger,ConcurrentHashMap<String,String> currentBuffer){
this.exchanger = exchanger;
this.currentBuffer = currentBuffer;
}
public void run() {
int count = 0;
while (currentBuffer != null) {
if (currentBuffer.size() == MAX_COUNT)
currentBuffer = exchanger.exchange(currentBuffer);
Thread.sleep(500);
String key = ""+count;
String value = ""+count;
currentBuffer.put(key,value);
System.out.println("生產緩存:"+value); count++;
}
}
}
static class EmptyingLoop implements Runnable {
private Exchanger<ConcurrentHashMap<String,String>> exchanger;
ConcurrentHashMap<String,String> currentBuffer;
public EmptyingLoop(Exchanger<ConcurrentHashMap<String,String>>
exchanger,ConcurrentHashMap<String,String> currentBuffer){
this.exchanger = exchanger;
this.currentBuffer = currentBuffer;
}
public void run() {
int count = 0;
while (currentBuffer != null) {
if (currentBuffer.size() == 0)
currentBuffer = exchanger.exchange(currentBuffer);
Thread.sleep(500);
String key = ""+count;
System.out.println("消費緩存:"+currentBuffer.get(key)); currentBuffer.remove(key);
count++;
}
}
}
public static void main(String[] args) {
Exchanger<ConcurrentHashMap<String,String>> exchanger = new Exchanger<ConcurrentHashMap<String,String>>();
ConcurrentHashMap<String,String> currentBuffer1 = new ConcurrentHashMap<String,String>(MAX_COUNT);
ConcurrentHashMap<String,String> currentBuffer2 = new ConcurrentHashMap<String,String>(MAX_COUNT);
new Thread(new FillingLoop(exchanger,currentBuffer1)).start();
new Thread(new EmptyingLoop(exchanger,currentBuffer2)).start();
}
}
Semaphore
Semaphore(信號量)是用來控制同時訪問特定資源的線程數量,它通過協調各個線程,以保證合理的使用公共 資源。可以用於做流量控制等。通過 acquire() 獲取一個許可,如果沒有就等待,release() 釋放一個許可。
public class SemaphoreExp {
public void method(Semaphore semaphore){
semaphore.acquire(); //進來
System.out.println(Thread.currentThread().getName()+" 執行。。。");
semaphore.release(); //釋放
}
public static void main(String[] args) {
SemaphoreExp d = new SemaphoreExp();
Semaphore semaphore = new Semaphore(10);//method方法同時只能10個線程執行
int count = 0;
while(count<30){
new Thread(new Runnable() {
@Override
public void run() {
d.method(semaphore);
}
}).start();
count++;
}
}
}