在日常開發中經常會遇到需要在主線程中開啓多個線程去並行執行任務,並且主線程需要等待所有子線程執行完畢後再進行彙總的場景。CountDownLatch、CyclicBarrier均是爲應對上面場景的類。此外還有Semaphore也可以起到阻塞的作用。
一、CountDownLatch
CountDownLatch(int count):構造方法,設置計數量;
void countDown():count減1;
long getCount():返回當前count;
boolean await():讓當前線程等待直到count減爲0。countDown()或者線程被中斷count都會減1;
boolean await(long timeout, TimeUnit unit):限制等待時間
例子
@Test
public void testCountDownLatch() throws InterruptedException {
CountDownLatch countDownLatch = new CountDownLatch(2);
Thread thread1 = new Thread(()-> {
System.out.println("thread1 run");
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
finally {
countDownLatch.countDown();
}
});
Thread thread2 = new Thread(()-> {
System.out.println("thread2 run");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
finally {
countDownLatch.countDown();
}
});
thread1.start();
thread2.start();
thread2.interrupt();
Thread.sleep(1000);
System.out.println("thread2 is interrupted,and Count is "+countDownLatch.getCount());
countDownLatch.await();
System.out.println("finally Count is "+countDownLatch.getCount());
System.out.println("end");
Thread.sleep(1000);
}
二、CyclicBarrier(循環屏障)
CountDownLatch 在解決多個線程同步方面對於調用線程的join方法已經有了不少優化。但還有一個問題是CountDownLatch 的計數器是一次性的,也就是等到計數器變爲0後,再調用CountDownLatch 的await和countdown方法都會立即返回,起不到線程同步的效果。而CyclicBarrier可以使計數器重置,並且不限於CountDownLatch 的功能。
例子1
@Test
public void testCyclicBarrier() {
CyclicBarrier cyclicBarrier =new CyclicBarrier(2,()-> {
System.out.println("執行線程"+Thread.currentThread()+"時,Barrier減爲0,觸發了執行後任務。");
});
ThreadPoolExecutor pool = new ThreadPoolExecutor(2, 2, 0,
TimeUnit.SECONDS, new LinkedBlockingQueue<>(
200));
pool.execute(()->{
System.out.println(Thread.currentThread()+" 開始執行");
try {
System.out.println(Thread.currentThread()+" cyclicBarrier 開始等待");
cyclicBarrier.await();
System.out.println(Thread.currentThread()+" cyclicBarrier 不再等待");
} catch (InterruptedException e) {
e.printStackTrace();
} catch (BrokenBarrierException e) {
e.printStackTrace();
}
});
pool.execute(()->{
System.out.println(Thread.currentThread()+" 開始執行");
try {
System.out.println(Thread.currentThread()+" cyclicBarrier 開始等待");
cyclicBarrier.await();
System.out.println(Thread.currentThread()+" cyclicBarrier 不再等待");
} catch (InterruptedException e) {
e.printStackTrace();
} catch (BrokenBarrierException e) {
e.printStackTrace();
}
});
pool.shutdown();
}
上面例子還不足以說明CyclicBarrier可循環利用,再舉一個例子
假設有任務分三個階段處理,但是需要全部線程的某一個階段全部完成纔可以進行下一個階段的工作。
@Test
public void testCyclicBarrier2() {
CyclicBarrier cyclicBarrier =new CyclicBarrier(2);
ThreadPoolExecutor pool = new ThreadPoolExecutor(2, 2, 0,
TimeUnit.SECONDS, new LinkedBlockingQueue<>(
200));
pool.execute(()->{
try {
System.out.println(Thread.currentThread()+" 階段1執行完畢!");
cyclicBarrier.await();
System.out.println(Thread.currentThread()+" 階段2執行完畢!");
cyclicBarrier.await();
System.out.println(Thread.currentThread()+" 階段3執行完畢!");
cyclicBarrier.await();
} catch (InterruptedException e) {
e.printStackTrace();
} catch (BrokenBarrierException e) {
e.printStackTrace();
}
});
pool.execute(()->{
try {
System.out.println(Thread.currentThread()+" 階段1執行完畢!");
cyclicBarrier.await();
System.out.println(Thread.currentThread()+" 階段2執行完畢!");
cyclicBarrier.await();
System.out.println(Thread.currentThread()+" 階段3執行完畢!");
cyclicBarrier.await();
} catch (InterruptedException e) {
e.printStackTrace();
} catch (BrokenBarrierException e) {
e.printStackTrace();
}
});
pool.shutdown();
}
打印結果:
三、Semaphore
信號量,不可以自動重置,主要三個方法
Semaphore(int permits):設置初始已獲得的許可數
void acquire(int permits):請求許可,當後面獲得相應數量的許可纔會放行,否則會阻塞當前線程。
void release():發放1個許可。
例子
@Test
public void testSemaphore() throws InterruptedException {
Semaphore semaphore = new Semaphore(0);
ThreadPoolExecutor pool = new ThreadPoolExecutor(2, 2, 0,
TimeUnit.SECONDS, new LinkedBlockingQueue<>(
200));
pool.execute(()->{
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread()+" 執行");
semaphore.release();
});
pool.execute(()->{
System.out.println(Thread.currentThread()+" 執行.");
semaphore.release();
});
semaphore.acquire(2);
System.out.println("線程全部執行完畢");
Thread.sleep(100);
pool.shutdown();
}
上面的例子中,假如將“ Semaphore semaphore = new Semaphore(0);”改爲“ Semaphore semaphore = new Semaphore(2);”,那麼semaphore.acquire(2);將不會等待,因爲初始化已經獲得了相應數量的許可。