(一)等待多线程完成的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);
}
}