目錄
java併發編程三劍客
思維導圖
CountDownLatch用法
位於java.util.concurrent包下,利用它可以實現類似計數器的功能,比如有一個任務A,它要等待其他幾個任務執行完畢後才能執行,這時可以使用CountDownLatch來實現這個功能
構造器以及方法
構造器
只提供了一種構造器,其中參數count爲計數器
public CountDownLatch(int count) {
if (count < 0) throw new IllegalArgumentException("count < 0");
this.sync = new Sync(count);
}
主要方法
// 調用await()方法的線程會被掛起,它會等待直到count的值爲0才繼續執行
public void await() throws InterruptedException {
sync.acquireSharedInterruptibly(1);
}
// 和await()方法類似,只不過添加了一個等待時間,時間到後也會執行
public boolean await(long timeout, TimeUnit unit)
throws InterruptedException {
return sync.tryAcquireSharedNanos(1, unit.toNanos(timeout));
}
// 將count的值減一
public void countDown() {
sync.releaseShared(1);
}
使用方法
import java.util.concurrent.CountDownLatch;
public class Test{
public static void main(String[] args) {
final CountDownLatch latch = new CountDownLatch(2);
new Thread(){
@Override
public void run(){
try{
System.out.println("子線程"+Thread.currentThread().getName()+"正在執行");
Thread.sleep(3000);
System.out.println("子線程"+Thread.currentThread().getName()+"執行完成");
latch.countDown();
}catch (InterruptedException e){
e.printStackTrace();
}
};
}.start();
new Thread(){
@Override
public void run(){
try {
System.out.println("子線程"+Thread.currentThread().getName()+"正在執行");
Thread.sleep(3000);
System.out.println("子線程"+Thread.currentThread().getName()+"執行完畢");
latch.countDown();
}catch (InterruptedException e){
e.printStackTrace();
}
};
}.start();
try {
System.out.println("等待兩個子線程執行完畢...");
latch.await();
System.out.println("兩個子線程執行完畢");
System.out.println("繼續執行主線程");
}catch (InterruptedException e){
e.printStackTrace();
}
}
}
- 運行結果:
CyclicBarrier用法
位於java.util.concurrent包下,字面意思迴環柵欄,通過它可以實現讓一組線程等待直到某個狀態後再全部同時執行,叫做迴環是因爲當所有等待線程都被釋放以後,CyclicBarrier可以重新使用。我們把這個狀態稱爲barrier。當調用await()方法以後,線程就處於barrier狀態
構造器以及主要方法
構造器
提供了兩種構造器
// 參數parties是指讓多少個線程或者任務等待至barrier狀態
// 參數barrierAction爲當前這些線程都達到barrier狀態時,會執行的內容
public CyclicBarrier(int parties, Runnable barrierAction) {
if (parties <= 0) throw new IllegalArgumentException();
this.parties = parties;
this.count = parties;
this.barrierCommand = barrierAction;
}
public CyclicBarrier(int parties) {
this(parties, null);
}
主要方法
await方法
// 比較常用,用來掛起當前線程,直至所有線程都達到barrier狀態再同時執行後續任務
public int await() throws InterruptedException, BrokenBarrierException {
try {
return dowait(false, 0L);
} catch (TimeoutException toe) {
throw new Error(toe); // cannot happen
}
}
// 讓這些線程等待至一定的時間,如果還有線程沒有到達barrier狀態就直接讓達到barrier狀態的線程執行後續任務
public int await(long timeout, TimeUnit unit)
throws InterruptedException,
BrokenBarrierException,
TimeoutException {
return dowait(true, unit.toNanos(timeout));
}
其中主要調用了dowait
private int dowait(boolean timed, long nanos)
throws InterruptedException, BrokenBarrierException,
TimeoutException {
final ReentrantLock lock = this.lock;
// 獲取鎖,顯然每次只有一個線程能獲取到對象的鎖,
lock.lock();
try {
// 判斷是否處於下一代,默認g.broken=false;
final Generation g = generation;
if (g.broken)
throw new BrokenBarrierException();
// 如果線程被中斷調用breadBarrier退出屏障並拋出異常
if (Thread.interrupted()) {
breakBarrier();
throw new InterruptedException();
}
// 減少線程達到屏障線程數
int index = --count;
// 如果所有線程達到屏障,喚醒其他線程繼續執行
if (index == 0) { // tripped
boolean ranAction = false;
try {
// 獲取需要指向的Runnable對象,如果不爲null則執行run方法
final Runnable command = barrierCommand;
if (command != null)
command.run();
// 設置執行方法完成
ranAction = true;
// 通知其他線程繼續執行並重置下一代
nextGeneration();
return 0;
} finally {
if (!ranAction)
breakBarrier();
}
}
// 如果還有其他線程沒有達到屏障將執行下面的循環
for (;;) {
try {
// 是否是超時等待,不是超時等待立馬調用trip.await(),trip是Condition,調用await將會是線程阻塞,否則調用帶有超時時間的awaitnanos(nanos)(超時時間大於0的情況下)
if (!timed)
trip.await();
else if (nanos > 0L)
nanos = trip.awaitNanos(nanos);
} catch (InterruptedException ie) {
if (g == generation && ! g.broken) {
breakBarrier();
throw ie;
} else {
Thread.currentThread().interrupt();
}
}
// 如果設置了超時且過了超時時間,查看當前代是否被破壞,破壞拋出異常
if (g.broken)
throw new BrokenBarrierException();
// 不是當前代返回
if (g != generation)
return index;
// 設置了超時且超時時間小於0,設置當前代被破壞同時喚醒其他線程並拋出超時異常
if (timed && nanos <= 0L) {
breakBarrier();
throw new TimeoutException();
}
}
} finally {
lock.unlock();
}
}
使用方法
import java.util.concurrent.BrokenBarrierException;
import java.util.concurrent.CyclicBarrier;
public class Test {
public static void main(String[] args) {
int N = 4;
CyclicBarrier barrier = new CyclicBarrier(N,new Runnable() {
@Override
public void run() {
System.out.println("當前線程"+Thread.currentThread().getName());
}
});
for(int i=0;i<N;i++) {
new Writer(barrier).start();
}
}
static class Writer extends Thread{
private CyclicBarrier cyclicBarrier;
public Writer(CyclicBarrier cyclicBarrier) {
this.cyclicBarrier = cyclicBarrier;
}
@Override
public void run() {
System.out.println("線程"+Thread.currentThread().getName()+"正在寫入數據...");
try {
Thread.sleep(5000); //以睡眠來模擬寫入數據操作
System.out.println("線程"+Thread.currentThread().getName()+"寫入數據完畢,等待其他線程寫入完畢");
cyclicBarrier.await();
} catch (InterruptedException e) {
e.printStackTrace();
}catch(BrokenBarrierException e){
e.printStackTrace();
}
System.out.println("所有線程寫入完畢,繼續處理其他任務...");
}
}
}
-
運行結果
Semaphore用法
信號量,可以控制同時訪問的線程個數,通過acquire()獲取一個許可,如果沒有就等待,而release釋放一個許可
位於java.util.concurrent包下,
構造器和主要方法
構造器
// 參數permits表示許可數目,即同時可以允許多少線程進行訪問
public Semaphore(int permits) {
sync = new NonfairSync(permits);
}
// fair表示是否是公平的,即等待時間越久的越先獲得許可
public Semaphore(int permits, boolean fair) {
sync = fair ? new FairSync(permits) : new NonfairSync(permits);
}
主要方法
- acquire 若無許可可以獲取,則一直等待直到獲得許可
- release 在釋放許可以前,必須先獲得許可
// 嘗試獲取一個許可,
public void acquire() throws InterruptedException {
sync.acquireSharedInterruptibly(1);
}
// 獲取peimits個許可
public void acquire(int permits) throws InterruptedException {
if (permits < 0) throw new IllegalArgumentException();
sync.acquireSharedInterruptibly(permits);
}
// 釋放一個許可
public void release() {
sync.releaseShared(1);
}
// 釋放permits個許可
public void release(int permits) {
if (permits < 0) throw new IllegalArgumentException();
sync.releaseShared(permits);
}
- 以上四個方法都會被阻塞,如果想立即得到執行結果
//嘗試獲取一個許可,若獲取成功,則立即返回true,若獲取失敗就立即返回false
public boolean tryAcquire() {
return sync.nonfairTryAcquireShared(1) >= 0;
}
// 嘗試獲取一個許可,若在指定的時間內獲取成功則立即返回true,否則立即返回false
public boolean tryAcquire(long timeout, TimeUnit unit)
throws InterruptedException {
return sync.tryAcquireSharedNanos(1, unit.toNanos(timeout));
}
// 嘗試獲取permits個許可,若獲取成功則立即返回true,或獲取失敗則立即返回false
public boolean tryAcquire(int permits) {
if (permits < 0) throw new IllegalArgumentException();
return sync.nonfairTryAcquireShared(permits) >= 0;
}
// 嘗試獲取permits個許可,若在指定時間內成功....
public boolean tryAcquire(int permits, long timeout, TimeUnit unit)
throws InterruptedException {
if (permits < 0) throw new IllegalArgumentException();
return sync.tryAcquireSharedNanos(permits, unit.toNanos(timeout));
}
使用方法
public static void main(String[] args) {
int N = 8; //工人數
Semaphore semaphore = new Semaphore(5); //機器數目
for(int i=0;i<N;i++)
new Worker(i,semaphore).start();
}
static class Worker extends Thread{
private int num;
private Semaphore semaphore;
public Worker(int num,Semaphore semaphore){
this.num = num;
this.semaphore = semaphore;
}
@Override
public void run() {
try {
semaphore.acquire();
System.out.println("工人"+this.num+"佔用一個機器在生產...");
Thread.sleep(2000);
System.out.println("工人"+this.num+"釋放出機器");
semaphore.release();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
- 結果
三種輔助類總結
- CountDownLatch和CyclicBarrier都能夠實現線程之間的等待,只不過它們側重點不同:
- CountDownLatch一般用於某個線程A等待若干個其他線程執行完任務之後,它才執行;
- CyclicBarrier一般用於一組線程互相等待至某個狀態,然後這一組線程再同時執行;
- CountDownLatch是不能夠重用的,而CyclicBarrier是可以重用的。
(InterruptedException e) {
e.printStackTrace();
}
}
}
* 結果
[外鏈圖片轉存中...(img-zsJDpEfe-1586999675681)]
### 三種輔助類總結
* CountDownLatch和CyclicBarrier都能夠實現線程之間的等待,只不過它們側重點不同:
* CountDownLatch一般用於某個線程A等待若干個其他線程執行完任務之後,它才執行;
* CyclicBarrier一般用於一組線程互相等待至某個狀態,然後這一組線程再同時執行;
* CountDownLatch是不能夠重用的,而CyclicBarrier是可以重用的。
* Semaphore其實和鎖有點類似,它一般用於控制對某組資源的訪問權限。