介绍
这个问题主要考察并发工具类CountDownLatch和CyclicBarrier的使用,原理问的较少,因为AQS讲清楚需要很深的功底,一般面试人员很少去详细问,就不详细解释原理。可网上搜索原理很全,本小节不深入原理,只演示下用法
CountDownLatch
简单理解,计数器,设定一个值每次线程任务执行后都会-1,值为0时候表示所有线程任务执行完毕,可以进行下一步操作
代码示例
public static void main(String[] args) {
//设定程序计数器初始值为10
CountDownLatch countDownLatch = new CountDownLatch(3);
Random random = new Random();
for (int i = 0; i < 3; i++) {
new Thread(() -> {
try {
//模拟线程执行时间 线程休眠随机0到10秒
int time = random.nextInt(10);
System.out.println(Thread.currentThread().getName() + "执行开始使用时间:"+time+"秒");
TimeUnit.SECONDS.sleep(time);
System.out.println(Thread.currentThread().getName() + "执行完毕");
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
//计数器减1
countDownLatch.countDown();
}
}).start();
}
try {
countDownLatch.await();
// countDownLatch.await(5,TimeUnit.SECONDS);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
System.out.println("所有线程执行完毕");
}
通过上面代码可以看到,只有等到所有线程都执行完毕后才会最后打印,即所有子线程都执行完毕才会执行主线程,走后续业务代码,实现这个功能主要靠CountDownLatch的两个方法.await()和.countDown()。
当然实际业务代码中设定计数器的值调整为传入的需要处理的数据集合.size()就好。 .await()为等待判断计数器是否为0,如果不为0则继续等待 .countDown() 代码计数器-1,证明子线程业务结束,可以释放.
注意事项
- 如果不调用.countDown() 主线程将会一直阻塞。
- 如果计数器初始值大于实际循环线程数。计数器不会归0,主线程将会一直阻塞。
- 如果计数器初始值小于实际循环线程数。计数器会提前归0,即子线程未全部结束,主线程已经执行。
- 如果实际业务允许,.await(5,TimeUnit.SECONDS)。带时间参数证明允许最多阻塞多长时间。时间到了执行主线程如图
CyclicBarrier
CyclicBarrier和CountDownLatch的使用很类似,CountDownLatch的计数器每个主线程只能使用一次,而CyclicBarrier的计数器可以使用reset()方法重置。
public static void main(String[] args) {
//初始化
CyclicBarrier cyclicBarrier = new CyclicBarrier(5, () -> {
System.out.println("所有线程都到达了屏障点");
});
Random random = new Random();
for (int i = 0; i < 5; i++) {
new Thread(() -> {
try {
int time = random.nextInt(5);
System.out.println(Thread.currentThread().getName() + "准备出发等待"+time+"秒");
TimeUnit.SECONDS.sleep(time);
System.out.println(Thread.currentThread().getName() + "已抵达屏障点");
cyclicBarrier.await();
} catch (Exception e) {
e.printStackTrace();
}
}).start();
}
}