文章目錄
Java併發包提供了哪些併發工具類?
我們通常所說的併發包也就是java.util.concurrent及其子包,集中了Java併發的各種基礎工具類,具體主要包括幾個方面:
- 提供了比synchronized更加高級的各種同步結構,包括CountDownLatch、 CyclicBarrier、 Semaphore等,可以實現更加豐富的多線程操作,比如利用Semaphore作爲資源
控制器,限制同時進行工作的線程數量。 - 各種線程安全的容器,比如最常見的ConcurrentHashMap、有序的ConcunrrentSkipListMap,或者通過類似快照機制,實現線程安全的動態數
組CopyOnWriteArrayList等。 - 各種併發隊列實現,如各種BlockedQueue實現,比較典型的ArrayBlockingQueue、 SynchorousQueue或針對特定場景的PriorityBlockingQueue等
- 強大的Executor框架,可以創建各種不同類型的線程池,調度任務運行等,絕大部分情況下,不再需要自己從頭實現線程池和任務調度器。
從jdk文檔中我們看到:
java8jdk在線
主要有3個包
java.util.concurrent
java.util.concurrent.atomic
java.util.concurrent.locks
這三個包中包含了上述的回答。
作者文章中主要關注了3個點,剩下的後面會有提到。
- CountDownLatch,允許一個或多個線程等待某些操作完成
- CyclicBarrier,一種輔助性的同步結構,允許多個線程等待到達某個屏障。
- Semaphore, Java版本的信號量實現。
Semaphore
基本API
Semaphore(int permits):構造方法
Semaphore(int permits,boolean fair):構造方法,當fair等於true時,創建具有給定許可數的計數信號量並設置爲公平信號量。
void acquire():從此信號量獲取一個許可前線程將一直阻塞。或線程爲 interrupted 。
acquire(int permits):每調用一次此方法,就獲得permits個許可。
release():釋放許可證,將其返回到信號量。
release(int permits):釋放給定數量(permits)的許可證,將其返回到信號量。
int availablePermits():返回此信號量中當前可用的許可數。
Semaphore使用
4步
//創建信號量
Semaphore semaphore = new Semaphore(5);
//創建線程
new Thread(new SemaphoreWorker(semaphore));
//獲取
semaphore.acquire();
//釋放
semaphore.release();
Semaphore情景:
隊伍一次進來5個人上車,等這5個人坐車出發,再放進去下一批
public class UsualSemaphoreSample {
public static void main(String[] args) throws InterruptedException {
System.out.println("Action...GO!");
Semaphore semaphore = new Semaphore(5);
for (int i = 0; i < 10; i++) {
Thread t = new Thread(new SemaphoreWorker(semaphore));
t.start();
}
}
}
class SemaphoreWorker implements Runnable {
private String name;
private Semaphore semaphore;
public SemaphoreWorker(Semaphore semaphore) {
this.semaphore = semaphore;
}
@Override
public void run() {
try {
log("is waiting for a permit!");
semaphore.acquire();
log("acquired a permit!");
log("executed!");
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
log("released a permit!");
semaphore.release();
}
}
private void log(String msg){
if (name == null) {
name = Thread.currentThread().getName();
}
System.out.println(name + " " + msg);
}
}
結果:
Action...GO!
Thread-0 is waiting for a permit!
Thread-2 is waiting for a permit!
Thread-1 is waiting for a permit!
Thread-1 acquired a permit!
Thread-1 executed!
Thread-1 released a permit!
Thread-0 acquired a permit!
Thread-0 executed!
Thread-2 acquired a permit!
Thread-0 released a permit!
Thread-3 is waiting for a permit!
Thread-2 executed!
Thread-2 released a permit!
Thread-3 acquired a permit!
Thread-3 executed!
Thread-3 released a permit!
Thread-5 is waiting for a permit!
Thread-5 acquired a permit!
Thread-5 executed!
Thread-5 released a permit!
Thread-6 is waiting for a permit!
Thread-6 acquired a permit!
Thread-6 executed!
Thread-6 released a permit!
Thread-4 is waiting for a permit!
Thread-4 acquired a permit!
Thread-4 executed!
Thread-4 released a permit!
Thread-7 is waiting for a permit!
Thread-7 acquired a permit!
Thread-7 executed!
Thread-7 released a permit!
Thread-8 is waiting for a permit!
Thread-8 acquired a permit!
Thread-8 executed!
Thread-8 released a permit!
Thread-9 is waiting for a permit!
Thread-9 acquired a permit!
Thread-9 executed!
Thread-9 released a permit!
Process finished with exit code 0
線程試圖獲得工作允許,得到許可則進行任務,然後釋放許可,這時等待許可的其他線程,就可獲得許可進入工作狀態,直到全部處理結束。
缺點:
一直有5個人可以試圖乘車,如果有1個人出發了,立即就有排隊的人獲得
許可,而這並不完全符合我們前面的要求
修改
public class AbnormalSemaphoreSample {
public static void main(String[] args) throws InterruptedException {
Semaphore semaphore = new Semaphore(0);
for (int i = 0; i < 10; i++) {
Thread t = new Thread(new MyWorker(semaphore));
t.start();
}
System.out.println("Action...GO!");
semaphore.release(5);
System.out.println("Wait for permits of");
while (semaphore.availablePermits()!=0) {
Thread.sleep(100L);
}
System.out.println("Action...GO again!");
semaphore.release(5);
}
}
class MyWorker implements Runnable {
private Semaphore semaphore;
public MyWorker(Semaphore semaphore) {
this.semaphore = semaphore;
}
@Override
public void run() {
try {
semaphore.acquire();
System.out.println("Executed!"+ Thread.currentThread().getName());
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
結果:
Action...GO!
Wait for permits of
Executed!Thread-1
Executed!Thread-2
Executed!Thread-0
Executed!Thread-3
Executed!Thread-4
Action...GO again!
Executed!Thread-7
Executed!Thread-8
Executed!Thread-5
Executed!Thread-9
Executed!Thread-6
Process finished with exit code 0
CountDownLatch和CyclicBarrier的不同點
- CountDownLatch是不可以重置的,所以無法重用;而CyclicBarrier則沒有這種限制,可以重用。
- CountDownLatch的基本操作組合是countDown/await。調用await的線程阻塞等待countDown足夠的次數,不管你是在一個線程還是多個線程裏countDown,只要次數足夠
即可。所以就像Brain Goetz說過的, CountDownLatch操作的是事件。 - CyclicBarrier的基本操作組合,則就是await,當所有的夥伴(parties)都調用了await,纔會繼續進行任務,並自動進行重置。 注意,正常情況下, CyclicBarrier的重置都是自
動發生的,如果我們調用reset方法,但還有線程在等待,就會導致等待線程被打擾,拋出BrokenBarrierException異常。 CyclicBarrier側重點是線程,而不是調用事件,它的
典型應用場景是用來等待併發線程結束。
CountDownLatch說明
上面的區別作者說完之後,對於我從來沒有用過的人來說還是不明白什麼意思。
主要補充以下問題。
-
判斷count不爲0的時,則當前線程呈wait狀態。
-
await的線程阻塞等待countDown足夠的次數(次數由構造方法提供),下面的次數爲6。
//創建6個CountDownLatch類對象
CountDownLatch latch = new CountDownLatch(6);
- countDown()方法,此方法將CountDownLatch(6)中6 減1
- await()方法 :當前線程等待,直到 減少到0.
- getCount()方法:通常用來測試用的,當前計數的值。
再看下面的代碼會好些。
CountDownLatch情景
假設有10個人排隊,我們將其分成5個人一批,一共兩個批次,通過CountDownLatch來協調批次。
import java.util.concurrent.CountDownLatch;
public class CountDownLatchSample {
public static void main(String[] args) throws InterruptedException {
CountDownLatch latch = new CountDownLatch(6);
for (int i = 0; i < 5; i++) {
Thread t = new Thread(new FirstBatchWorker(latch));
t.start();
}
for (int i = 0; i < 5; i++) {
Thread t = new Thread(new SecondBatchWorker(latch));
t.start();
}
// 注意這裏也是演示目的的邏輯,並不是推薦的協調方式
while ( latch.getCount() != 1 ){
Thread.sleep(100L);
}
System.out.println("Wait for first batch finish");
latch.countDown();
}
}
public class FirstBatchWorker implements Runnable {
private CountDownLatch latch;
public FirstBatchWorker(CountDownLatch latch) {
this.latch = latch;
}
@Override
public void run() {
System.out.println("First batch executed!"+Thread.currentThread().getName());
latch.countDown();
//每次countDown之後都會少1
System.out.println("First batch getCount"+ latch.getCount());
}
}
import java.util.concurrent.CountDownLatch;
public class SecondBatchWorker implements Runnable {
private CountDownLatch latch;
public SecondBatchWorker(CountDownLatch latch) {
this.latch = latch;
}
@Override
public void run() {
try {
latch.await();
System.out.println("Second batch executed!"+Thread.currentThread().getName());
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
結果:
First batch executed!Thread-1
First batch getCount5
First batch executed!Thread-2
First batch executed!Thread-0
First batch getCount4
First batch getCount3
First batch executed!Thread-4
First batch getCount2
First batch executed!Thread-3
First batch getCount1
Wait for first batch finish
Second batch executed!Thread-8
Second batch executed!Thread-9
Second batch executed!Thread-5
Second batch executed!Thread-7
Second batch executed!Thread-6
Process finished with exit code 0
CyclicBarrier
作者說完之後還是不明白。。。自我總結如下:
-
從單詞上看,是循環,屏障的意思。不同點從字面上看出來了,CyclicBarrier是循環,CountDownLatch無法重用。
-
CyclicBarrier計數爲加法。
-
構造函數:CyclicBarrier(int parties, Runnable barrierAction)
- parties:是參與線程的個數,不能小於1
- Runnable :最後一個到達線程要做的任務
-
await()方法:線程調用 await() 表示自己已經到達Barrier
-
下面代碼5個線程都執行了await()方法都執行了,才執行下面的方法。否則線程彼此等待,一直呈阻塞狀態。
CyclicBarrier barrier = new CyclicBarrier(5, new Runnable() {
@Override
public void run() {
System.out.println("Action...GO again!");
}
});
CyclicBarrier情景
5個工作線程其實更像是代表了5個可以就緒空車,而不再是5個乘客,對比前面CountDownLatch的例子更有助於我們區別它們的抽象模型。
public class CyclicBarrierSample {
public static void main(String[] args) throws InterruptedException {
CyclicBarrier barrier = new CyclicBarrier(5, new Runnable() {
@Override
public void run() {
//5個線程執行了纔會去執行此方法。
System.out.println("Action...GO again!");
}
});
//5個線程
for (int i = 0; i < 5; i++) {
Thread t = new Thread(new CyclicWorker(barrier));
t.start();
}
}
static class CyclicWorker implements Runnable {
private CyclicBarrier barrier;
public CyclicWorker(CyclicBarrier barrier) {
this.barrier = barrier;
}
@Override
public void run() {
try {
//結果循環了3次 自動重置
for (int i = 0; i < 3; i++) {
System.out.println(Thread.currentThread().getName()+" Executed!");
barrier.await();
}
} catch (BrokenBarrierException e) {
e.printStackTrace();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
結果:
Thread-1 Executed!
Thread-0 Executed!
Thread-2 Executed!
Thread-3 Executed!
Thread-4 Executed!
Action...GO again!
Thread-4 Executed!
Thread-2 Executed!
Thread-1 Executed!
Thread-3 Executed!
Thread-0 Executed!
Action...GO again!
Thread-0 Executed!
Thread-4 Executed!
Thread-1 Executed!
Thread-3 Executed!
Thread-2 Executed!
Action...GO again!