1.倒計時協作器CountDownLatch
CountDownLatch可以用來實現一個(或者多個)線程等待其他線程完成一組特定的操作之後繼續運行。
場景:cf房間中有10個玩家,9個玩家必須全部準備才能開始遊戲;
代碼實現:
public class CountDownLatchTest {
public static final CountDownLatch countDownLatch = new CountDownLatch(9);
public static void main(String[] args) {
new Thread(()->{
try {
countDownLatch.await();
System.out.println("遊戲開始,小心爆頭");
} catch (InterruptedException e) {
e.printStackTrace();
}
}).start();
for( int i =0;i<9;i++){
try {
Thread.sleep(50);
} catch (InterruptedException e) {
e.printStackTrace();
}
new Thread(()->{
countDownLatch.countDown();
System.out.println(Thread.currentThread().getName()+"玩家準備完畢");
}).start();
}
}
}
CountDownLatch內部會維護一個用戶表示未完成的先決操作數量的計數器。CountDownLatch.countDown()方法執行一次,計數器的數量就會減少1。CountDownLatch.await()方法在計數器不爲0的時候,當前線程會被暫停,當計數器達到0時,相應實例上等待的所有線程會別喚醒。
當計數器值達到0時,計數器的值不會再發生便會。此時調用CountDownLatch.countDown()不會有異常拋出,CountDownLatch.await()的線程也不會被暫停。CountDownLatch只能使用一次。CountDownLatch.getCount()方法可以查詢當前計數器的值。
2.柵欄CyclicBarrier
場景:lol遊戲,在選擇完英雄之後,必須等待10名玩家遊戲全部加載完成之後,一起進入遊戲,
CyclicBarrier的作用就是等待所有線程全部達到一個點之後再一起執行。
代碼實現:
public static final CyclicBarrier cyclicBarrier = new CyclicBarrier(10);
public static void main(String[] args) {
while (true){
for( int i =0;i<10;i++){
new Thread(()->{
try {
try {
//休眠一段時間,表示不同的加載時間
Random random = new Random();
Thread.sleep(random.nextInt(10000));
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+"玩家準備完畢");
//線程暫停,等待其他玩家加載成功,再一起進入遊戲
cyclicBarrier.await();
System.out.println(Thread.currentThread().getName()+"進入遊戲");
} catch (InterruptedException e) {
e.printStackTrace();
} catch (BrokenBarrierException e) {
e.printStackTrace();
}
}).start();
}
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
//getNumberWaiting 獲取等待的數量
while (cyclicBarrier.getNumberWaiting()!=0){
//遊戲沒有結束耐心等待
}
System.out.println("遊戲結束,等待遊戲結果");
System.out.println("下一把開始,請大家準備");
}
}
調用CyclicBarrier.await()方法,當前線程被暫停,當最後一個線程調用CyclicBarrier.await()方法時,會使得使用當前實例的暫停的所有線程喚醒。與CountDownLatch的不同點是,當所有線程被喚醒之後,下一次調用await()方法又會暫停,又需要等待最後的線程都執行之後才能喚醒,是可以重複使用的。
注意:CyclicBarrier經常用來模擬高併發的測試,如果一個線程需要等待另一個線程的話,在很多場景Thread.join()即可實現效果。
CyclicBarrier方法
//獲取調用await()方法線程的數量
public int getNumberWaiting();
//獲取屏障的數量
public int getParties();
//等待 返回值爲當前線程的索引,0表示當前線程是最後一個到達的線程
public int await();
public int await(long timeout, TimeUnit unit);
//此屏障是否處於斷開狀態
public boolean isBroken();
//將屏障重置爲初始狀態
//調用初始化之後再await()等的線程會拋異常
public void reset()
3.限流Semaphore
現在服務器有很多種限流的方式,Semaphore
是jdk提供的一種限流方式。
@RestController
public class TestController {
// 限流2 個
public static final Semaphore semaphore = new Semaphore(2);
@RequestMapping("/semaphore/test")
public String test(){
try {
semaphore.acquire();
System.out.println("1-獲取資源");
Thread.sleep(10000);
} catch (InterruptedException e) {
e.printStackTrace();
}finally {
semaphore.release();
}
return "success";
}
@RequestMapping("/semaphore/test2")
public String test2(){
try {
semaphore.acquire();
System.out.println("2-獲取資源");
Thread.sleep(10000);
} catch (InterruptedException e) {
e.printStackTrace();
}finally {
semaphore.release();
}
return "success";
}
@RequestMapping("/semaphore/test3")
public String test3(){
try {
boolean b = semaphore.tryAcquire(1,1000, TimeUnit.MILLISECONDS);
if(b){
System.out.println("3-獲取資源");
semaphore.release();
}else{
System.out.println("3-獲取資源失敗");
}
} catch (InterruptedException e) {
e.printStackTrace();
}
return "success";
}
}
按順序請求這三個方法輸出:
1-獲取資源
2-獲取資源
3-獲取資源失敗
當前限流2個,test和test2方法同時獲取資源後等待10秒釋放,test3方法試着獲取資源(等待1秒)。等test和test2有一個調用release釋放方法之後,test3就可以成功獲取資源。
注意:
- Semaphore.acquire/release方法要配對使用,使用acquire申請資源,使用release釋放資源。Semaphore即使在沒有申請到資源的情況下,還是可以通過release釋放資源,這裏就要自己通過代碼進行合適的處理。和鎖不同的是,鎖必須獲得鎖才能釋放鎖,這裏並沒有這種限制。
- Semaphore.release方法儘可能的放到finally中避免資源無法歸還。
- 如果Semaphore的配額爲1,那麼創建的實例相當與一個互斥鎖,由於可以在沒有申請資源的情況調用release釋放資源,所以,這裏允許一個線程釋放另一個線程的鎖。
- Semaphore在申請資源的時候不是公平的,因此,如果當前配額爲0 時,所有線程在申請的時候是等待狀態,一旦有線程釋放資源,會有等待的線程中任意一個申請成功。