(一)等待多線程完成的CountDownLatch
CountDownLatch允許一個或多個線程等待其他線程完成操作。
/**
* sheet2 解析完成
* sheet1 解析完成
* sheet3 解析完成
* 所有sheet解析完成
*/
public class JoinCountDownLatchTest {
public static void main(String[] args) throws InterruptedException {
Thread t1 =new Thread(()->{
System.out.println("sheet1 解析完成");
});
Thread t2 =new Thread(()->{
System.out.println("sheet2 解析完成");
});
Thread t3 =new Thread(()->{
System.out.println("sheet3 解析完成");
});
t1.start();
t2.start();
t3.start();
t1.join();
t2.join();
t3.join();
System.out.println("所有sheet解析完成");
}
}
原理:join用於讓當前執行線程等待join線程執行結束。其實現原理是不停檢查join線程是否存 活,如果join線程存活則讓當前線程永遠等待。
2.使用CountDownLatch併發工具類控制主線程最後執行
/**
* Thread-0: 解析完成
* Thread-2: 解析完成
* Thread-1: 解析完成
* 所有sheet解析完成
*/
public class CountDownLatchTest {
public static void main(String[] args) throws InterruptedException {
//1.計數器初始值爲3
CountDownLatch countDownLatch=new CountDownLatch(3);
for (int i = 0; i < 3; i++) {
new Thread(()->{
System.out.println(Thread.currentThread().getName()+": 解析完成");
try {
Thread.sleep(300);
//2.每次減一
countDownLatch.countDown();
} catch (InterruptedException e) {
e.printStackTrace();
}
}).start();
}
//3.await方法 會阻塞當前線程,直到N變成零
countDownLatch.await();
System.out.println("所有sheet解析完成");
}
}
注意:計數器必須大於等於0,只是等於0時候,計數器就是零,調用await方法時不會 阻塞當前線程。CountDownLatch不可能重新初始化或者修改CountDownLatch對象的內部計數 器的值。一個線程調用countDown方法happen-before,另外一個線程調await方法。
(二)同步屏障CyclicBarrier
public class CyclicBarrierTest {
public static void main(String[] args) throws BrokenBarrierException, InterruptedException {
//1.參數2表示屏障攔截的線程數量
CyclicBarrier cyclicBarrier = new CyclicBarrier(2);
new Thread(()->{
try {
//2.子線程調用await方法告訴訴CyclicBarrier我已經到達了屏障
cyclicBarrier.await();
System.out.println("sheet1");
} catch (InterruptedException e) {
e.printStackTrace();
} catch (BrokenBarrierException e) {
e.printStackTrace();
}
}).start();
//3.主線程調用await方法告訴訴CyclicBarrier我已經到達了屏障
cyclicBarrier.await();
System.out.println("sheet2");
}
}
注意:
一般是主線程優先於子線程執行,但在這裏主線程和子線程的調度是由CPU決定的,兩個線程都有可能先執行,所以會產生兩種輸出,第一種:sheet1、sheet2 第二種:sheet2、sheet1。
2. CyclicBarrier的應用場景
public class BankWaterService implements Runnable {
/*** 創建4個屏障,處理完之後執行當前類的run方法 */
private CyclicBarrier c = new CyclicBarrier(4, this);
/*** 假設只有4個sheet,所以只啓動4個線程 */
private Executor executor = Executors.newFixedThreadPool(4);
/*** 保存每個sheet計算出的銀流結果 */
private ConcurrentHashMap<String, Integer> sheetBankWaterCount = new ConcurrentHashMap<String, Integer>();
private void count(){
for (int i = 0; i < 4; i++) {
executor.execute(()->{
sheetBankWaterCount.put(Thread.currentThread().getName(),1);
try {
c.await();
} catch (InterruptedException e) {
e.printStackTrace();
} catch (BrokenBarrierException e) {
e.printStackTrace();
}
});
}
}
//到達屏障攔截的線程數量,執行以下線程
@Override
public void run() {
int result = 0;
for(Map.Entry<String, Integer> sheet : sheetBankWaterCount.entrySet()){
result = result+sheet.getValue();
}
System.out.println("result = " + result);
}
public static void main(String[] args) {
BankWaterService bankWaterService = new BankWaterService();
bankWaterService.count();
}
}
3.CyclicBarrier和CountDownLatch的區別
- CountDownLatch是線程組之間的等待,即一個(或多個)線程等待N個線程完成某件事情之後再執行;而CyclicBarrier則是線程組內的等待,即每個線程相互等待,即N個線程都被攔截之後,然後依次執行。
- CountDownLatch是減計數方式,而CyclicBarrier是加計數方式。
- CountDownLatch計數爲0無法重置,而CyclicBarrier計數達到初始值,則可以重置。
- CountDownLatch不可以複用,而CyclicBarrier可以複用。
(三)控制併發線程數的Semaphore
@Slf4j
public class SemaphoreTest {
private final static int threadCount = 30;
public static void main(String[] args) throws Exception {
ExecutorService exec = Executors.newCachedThreadPool();
//表示允 許3個線程獲取許可證,也就是最大併發數是3
final Semaphore semaphore = new Semaphore(3);
for (int i = 0; i < threadCount; i++) {
final int threadNum = i;
exec.execute(() -> {
try {
// 獲取一個許可
semaphore.acquire();
//一次三個線程執行test方法
test(threadNum);
// 釋放一個許可
semaphore.release();
} catch (Exception e) {
log.error("exception", e);
}
});
}
exec.shutdown();
}
private static void test(int threadNum) throws Exception {
log.info(Thread.currentThread().getName()+" is running");
Thread.sleep(10000);
}
}