前言
之前學多執行緒的時候沒有學習執行緒的同步工具類(輔助類)。ps:當時覺得暫時用不上,認為是挺高深的知識點就沒去管了..
在前幾天,朋友發了一篇比較好的Semaphore文章過來,然後在瀏覽部落格的時候又發現面試還會考,那還是挺重要的知識點。於是花了點時間去了解一下。
Java為我們提供了三個同步工具類:
- CountDownLatch(閉鎖)
- CyclicBarrier(柵欄)
- Semaphore(訊號量)
這幾個工具類其實說白了就是為了能夠更好控制執行緒之間的通訊問題~
一、CountDownLatch
1.1CountDownLatch簡介
- A synchronization aid that allows one or more threads to wait until a set of operations being performed in other threads completes.
簡單來說:CountDownLatch是一個同步的輔助類,允許一個或多個執行緒一直等待,直到其它執行緒完成它們的操作。
它常用的API其實就兩個:await()
和countDown()
使用說明:
- count初始化CountDownLatch,然後需要等待的執行緒呼叫await方法。await方法會一直受阻塞直到count=0。而其它執行緒完成自己的操作後,呼叫
countDown()
使計數器count減1。當count減到0時,所有在等待的執行緒均會被釋放 - 說白了就是通過count變數來控制等待,如果count值為0了(其他執行緒的任務都完成了),那就可以繼續執行。
1.2CountDownLatch例子
例子:3y現在去做實習生了,其他的員工還沒下班,3y不好意思先走,等其他的員工都走光了,3y再走。
import java.util.concurrent.CountDownLatch;
public class Test {
public static void main(String[] args) {
final CountDownLatch countDownLatch = new CountDownLatch(5);
System.out.println("現在6點下班了.....");
// 3y執行緒啟動
new Thread(new Runnable() {
@Override
public void run() {
try {
// 這裡呼叫的是await()不是wait()
countDownLatch.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("...其他的5個員工走光了,3y終於可以走了");
}
}).start();
// 其他員工執行緒啟動
for (int i = 0; i < 5; i++) {
new Thread(new Runnable() {
@Override
public void run() {
System.out.println("員工xxxx下班了");
countDownLatch.countDown();
}
}).start();
}
}
}
複製程式碼
輸出結果:
再寫個例子:3y現在負責倉庫模組功能,但是能力太差了,寫得很慢,別的員工都需要等3y寫好了才能繼續往下寫。
import java.util.concurrent.CountDownLatch;
public class Test {
public static void main(String[] args) {
final CountDownLatch countDownLatch = new CountDownLatch(1);
// 3y執行緒啟動
new Thread(new Runnable() {
@Override
public void run() {
try {
Thread.sleep(5);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("3y終於寫完了");
countDownLatch.countDown();
}
}).start();
// 其他員工執行緒啟動
for (int i = 0; i < 5; i++) {
new Thread(new Runnable() {
@Override
public void run() {
System.out.println("其他員工需要等待3y");
try {
countDownLatch.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("3y終於寫完了,其他員工可以開始了!");
}
}).start();
}
}
}
複製程式碼
輸出結果:
參考資料:
二、CyclicBarrier
2.1CyclicBarrier簡介
- A synchronization aid that allows a set of threads to all wait for each other to reach a common barrier point. CyclicBarriers are useful in programs involving a fixed sized party of threads that must occasionally wait for each other. The barrier is called cyclic because it can be re-used after the waiting threads are released.
簡單來說:CyclicBarrier允許一組執行緒互相等待,直到到達某個公共屏障點。叫做cyclic是因為當所有等待執行緒都被釋放以後,CyclicBarrier可以被重用(對比於CountDownLatch是不能重用的)
使用說明:
- CountDownLatch注重的是等待其他執行緒完成,CyclicBarrier注重的是:當執行緒到達某個狀態後,暫停下來等待其他執行緒,所有執行緒均到達以後,繼續執行。
2.2CyclicBarrier例子
例子:3y和女朋友約了去廣州夜上海吃東西,由於3y和3y女朋友住的地方不同,自然去的路徑也就不一樣了。於是他倆約定在體育西路地鐵站集合,約定等到相互見面的時候就發一條朋友圈。
import java.util.concurrent.BrokenBarrierException;
import java.util.concurrent.CyclicBarrier;
public class Test {
public static void main(String[] args) {
final CyclicBarrier CyclicBarrier = new CyclicBarrier(2);
for (int i = 0; i < 2; i++) {
new Thread(() -> {
String name = Thread.currentThread().getName();
if (name.equals("Thread-0")) {
name = "3y";
} else {
name = "女朋友";
}
System.out.println(name + "到了體育西");
try {
// 兩個人都要到體育西才能發朋友圈
CyclicBarrier.await();
// 他倆到達了體育西,看見了對方發了一條朋友圈:
System.out.println("跟" + name + "去夜上海吃東西~");
} catch (InterruptedException e) {
e.printStackTrace();
} catch (BrokenBarrierException e) {
e.printStackTrace();
}
}).start();
}
}
}
複製程式碼
測試結果:
玩了一天以後,各自回到家裡,3y和女朋友約定各自洗澡完之後再聊天
import java.util.concurrent.BrokenBarrierException;
import java.util.concurrent.CyclicBarrier;
public class Test {
public static void main(String[] args) {
final CyclicBarrier CyclicBarrier = new CyclicBarrier(2);
for (int i = 0; i < 2; i++) {
new Thread(() -> {
String name = Thread.currentThread().getName();
if (name.equals("Thread-0")) {
name = "3y";
} else {
name = "女朋友";
}
System.out.println(name + "到了體育西");
try {
// 兩個人都要到體育西才能發朋友圈
CyclicBarrier.await();
// 他倆到達了體育西,看見了對方發了一條朋友圈:
System.out.println("跟" + name + "去夜上海吃東西~");
// 回家
CyclicBarrier.await();
System.out.println(name + "洗澡");
// 洗澡完之後一起聊天
CyclicBarrier.await();
System.out.println("一起聊天");
} catch (InterruptedException e) {
e.printStackTrace();
} catch (BrokenBarrierException e) {
e.printStackTrace();
}
}).start();
}
}
}
複製程式碼
測試結果:
參考資料:
三、Semaphore
3.1Semaphore簡介
Semaphores are often used to restrict the number of threads than can access some (physical or logical) resource.
- A counting semaphore. Conceptually, a semaphore maintains a set of permits. Each {@link #acquire} blocks if necessary until a permit is available, and then takes it. Each {@link #release} adds a permit,potentially releasing a blocking acquirer.However, no actual permit objects are used; the {@code Semaphore} just keeps a count of the number available and acts accordingly.
Semaphore(訊號量)實際上就是可以控制同時訪問的執行緒個數,它維護了一組**"許可證"**。
- 當呼叫
acquire()
方法時,會消費一個許可證。如果沒有許可證了,會阻塞起來 - 當呼叫
release()
方法時,會新增一個許可證。 - 這些"許可證"的個數其實就是一個count變數罷了~
3.2Semaphore例子
3y女朋友開了一間賣酸奶的小店,小店一次只能容納5個顧客挑選購買,超過5個就需要排隊啦~~~
import java.util.concurrent.Semaphore;
public class Test {
public static void main(String[] args) {
// 假設有50個同時來到酸奶店門口
int nums = 50;
// 酸奶店只能容納10個人同時挑選酸奶
Semaphore semaphore = new Semaphore(10);
for (int i = 0; i < nums; i++) {
int finalI = i;
new Thread(() -> {
try {
// 有"號"的才能進酸奶店挑選購買
semaphore.acquire();
System.out.println("顧客" + finalI + "在挑選商品,購買...");
// 假設挑選了xx長時間,購買了
Thread.sleep(1000);
// 歸還一個許可,後邊的就可以進來購買了
System.out.println("顧客" + finalI + "購買完畢了...");
semaphore.release();
} catch (InterruptedException e) {
e.printStackTrace();
}
}).start();
}
}
}
複製程式碼
輸出結果:
反正每次只能5個客戶同時進酸奶小店購買挑選。
參考資料:
四、總結
Java為我們提供了三個同步工具類:
- CountDownLatch(閉鎖)
- 某個執行緒等待其他執行緒執行完畢後,它才執行(其他執行緒等待某個執行緒執行完畢後,它才執行)
- CyclicBarrier(柵欄)
- 一組執行緒互相等待至某個狀態,這組執行緒再同時執行。
- Semaphore(訊號量)
- 控制一組執行緒同時執行。
本文簡單的介紹了一下這三個同步工具類是幹嘛用的,要深入還得看原始碼或者借鑑其他的資料。
最後補充一下之前的思維導圖知識點:
參考資料:
- 《Java併發程式設計實戰》
- www.cnblogs.com/dolphin0520…
- zhuanlan.zhihu.com/p/27829595
如果文章有錯的地方歡迎指正,大家互相交流。習慣在微信看技術文章,想要獲取更多的Java資源的同學,可以關注微信公眾號:Java3y。
文章的目錄導航: