在Java里面有幾種可以用於控制線程狀態的方法,如CountDownLatch計數器、CyclicBarrier循環柵欄、Sempahore信號量。下面就分別演示下他們的使用方法:
CountDownLatch
CountDownLatch可以實現多線程之間的計數器,並實現阻塞功能。比如某個任務依賴於其他的兩個任務,只有那兩個任務執行結束后,它才能執行。
import java.util.concurrent.CountDownLatch;
public class CountDownLatchTest2 {
public static void main(String[] args) {
// 創建計數器,初始化為2
final CountDownLatch latch = new CountDownLatch(2);
new Thread(() -> {
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(() -> {
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("等待2個子線程執行完畢...");
// 阻塞
latch.await();
System.out.println("2個子線程已經執行完畢");
System.out.println("繼續執行主線程");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
執行的結果:
子線程Thread-0正在執行
等待2個子線程執行完畢...
子線程Thread-1正在執行
子線程Thread-0執行完畢
子線程Thread-1執行完畢
2個子線程已經執行完畢
繼續執行主線程
如上圖所示,線程1需要另兩個線程結束后,才能繼續執行。那么就可以在兩個縣城里面執行countDown(),然后主線程調用await()進行阻塞。
CyclicBarrier 循環柵欄
它有兩層含義,一個是柵欄,一個是循環。先看柵欄,意思就是想一堵牆一樣,可以同時對多個線程狀態進行管理。
如圖所示,幾個線程必須同時執行完,才能繼續:
import java.util.concurrent.BrokenBarrierException;
import java.util.concurrent.CyclicBarrier;
public class CyclicBarrierTest {
public static void main(String[] args) {
int N = 4;
CyclicBarrier barrier = new CyclicBarrier(N);
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("所有線程寫入完畢,繼續處理其他任務...");
}
}
}
輸出:
線程Thread-0正在寫入數據...
線程Thread-1正在寫入數據...
線程Thread-2正在寫入數據...
線程Thread-3正在寫入數據...
線程Thread-0寫入數據完畢,等待其他線程寫入完畢
線程Thread-1寫入數據完畢,等待其他線程寫入完畢
線程Thread-3寫入數據完畢,等待其他線程寫入完畢
線程Thread-2寫入數據完畢,等待其他線程寫入完畢
所有線程寫入完畢,繼續處理其他任務...
所有線程寫入完畢,繼續處理其他任務...
所有線程寫入完畢,繼續處理其他任務...
所有線程寫入完畢,繼續處理其他任務...
循環的意思就是當計數減到0時,還可以繼續使用,如:
import java.util.concurrent.BrokenBarrierException;
import java.util.concurrent.CyclicBarrier;
public class CyclicBarrierTest3 {
public static void main(String[] args) {
int N = 4;
CyclicBarrier barrier = new CyclicBarrier(N);
for(int i=0;i<N;i++) {
new Writer(barrier).start();
}
try {
Thread.sleep(25000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("CyclicBarrier重用");
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(Thread.currentThread().getName()+"所有線程寫入完畢,繼續處理其他任務...");
}
}
}
輸出:
線程Thread-0正在寫入數據...
線程Thread-2正在寫入數據...
線程Thread-3正在寫入數據...
線程Thread-1正在寫入數據...
線程Thread-2寫入數據完畢,等待其他線程寫入完畢
線程Thread-0寫入數據完畢,等待其他線程寫入完畢
線程Thread-1寫入數據完畢,等待其他線程寫入完畢
線程Thread-3寫入數據完畢,等待其他線程寫入完畢
Thread-3所有線程寫入完畢,繼續處理其他任務...
Thread-1所有線程寫入完畢,繼續處理其他任務...
Thread-0所有線程寫入完畢,繼續處理其他任務...
Thread-2所有線程寫入完畢,繼續處理其他任務...
CyclicBarrier重用
線程Thread-4正在寫入數據...
線程Thread-5正在寫入數據...
線程Thread-6正在寫入數據...
線程Thread-7正在寫入數據...
線程Thread-4寫入數據完畢,等待其他線程寫入完畢
線程Thread-7寫入數據完畢,等待其他線程寫入完畢
線程Thread-5寫入數據完畢,等待其他線程寫入完畢
線程Thread-6寫入數據完畢,等待其他線程寫入完畢
Thread-6所有線程寫入完畢,繼續處理其他任務...
Thread-7所有線程寫入完畢,繼續處理其他任務...
Thread-4所有線程寫入完畢,繼續處理其他任務...
Thread-5所有線程寫入完畢,繼續處理其他任務...
Semaphore信號量
這個東西有點像連接池的感覺,某一時間只有幾個線程能拿到資源,執行操作。
比如下面車間工人在排隊使用機器的例子:
import java.util.concurrent.Semaphore;
public class SemaphoreTest {
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();
}
}
}
}
輸出:
工人0占用一個機器在生產...
工人1占用一個機器在生產...
工人2占用一個機器在生產...
工人3占用一個機器在生產...
工人4占用一個機器在生產...
工人1釋放出機器
工人0釋放出機器
工人4釋放出機器
工人5占用一個機器在生產...
工人2釋放出機器
工人7占用一個機器在生產...
工人3釋放出機器
工人6占用一個機器在生產...
工人5釋放出機器
工人6釋放出機器
工人7釋放出機器
總結
- CountDownLatch 可以實現計數等待,主要用於某個線程等待其他幾個線程
- CyclicBarrier 實現循環柵欄,主要用於多個線程同時等待其他線程
- Semaphore 信號量,主要強調只有某些個數量的線程能拿到資源執行