本文主要讨论在高并发编程中两非常实用工具CyclicBarrier(同步屏障)和CountDownLatch(倒计时锁),两者都是java.util.concurrent并发包内非常有用的并发工具类,为了帮助理解会结合一些有趣的比喻,下面将对两者进行讨论。
一、CountDownLatch倒计时锁(一个线程等待另外N个线程完成某个事情之后才能执行)
//创建一个倒计时锁,设置值为5
final CountDownLatch latch = new CountDownLatch(5);
try {
//启用5个线程
for (int i = 1; i <= 5; i++) {
new Thread(new Runnable() {
@Override
public void run() {
try {
Thread.sleep(1000);
} catch (Exception e) {
e.printStackTrace();
}
System.out.println("子线程执行!");
//让latch锁中的数值减1
latch.countDown();
}
}).start();
}
//处于阻塞状态直到latch中数值为零才执行后续操作
latch.await();
System.out.println("主线程执行");
} catch (Exception e) {
System.out.println("捕获异常");
}
}
- 1.
- 2.
- 3.
- 4.
- 5.
- 6.
- 7.
- 8.
- 9.
- 10.
- 11.
- 12.
- 13.
- 14.
- 15.
- 16.
- 17.
- 18.
- 19.
- 20.
- 21.
- 22.
- 23.
- 24.
- 25.
- 26.
运行结果:
解析:倒计时锁理解起来比较容易,这里通过结合实际场景帮助理解。场景:一张数据表中存放大量的数据,现要读取表里的所有信息。为了提高读取效率便通过在主线程中开启多个子线程分工合作对数据表进行读取。接下来要等待全部子线程读取完毕之后,将读取到的内容进行汇总并在主线程中进行处理。
- 首先先设置一个CountDownLatch倒计时锁 ,并设置倒计时值为5
- 每一个子线程进行自己的工作,当工作执行完毕完毕后通过执行latch.countDown()对倒计时锁的值进行减1操作,表示自己工作完成。
- 主线程latch.await() 后的代码段一直属于等待状态,直到CountDownLatch的值为0时才继续执行。
二、可循环使用的屏障CyclicBarrier(N个线程相互等待,任何一个线程完成之前,所有的线程都必须等待)
//建立一个屏障并设定一个值,当有足够的线程达到屏障时再一起释放
CyclicBarrier barrier = new CyclicBarrier(5, () -> {
System.out.println("开始游戏");
});
ExecutorService executorPool = Executors.newCachedThreadPool();
for (int i = 1; i <= 5; i++) {
int num = i;
Thread.sleep(1000);
executorPool.execute(() -> {
try {
System.out.println(num + "号玩家,已准备好,等待进入游戏");
barrier.await();
System.out.println(num + "号玩家,已经进入游戏");
} catch (InterruptedException e) {
e.printStackTrace();
} catch (BrokenBarrierException e) {
e.printStackTrace();
}
});
}
executorPool.shutdown();
- 1.
- 2.
- 3.
- 4.
- 5.
- 6.
- 7.
- 8.
- 9.
- 10.
- 11.
- 12.
- 13.
- 14.
- 15.
- 16.
- 17.
- 18.
- 19.
- 20.
- 21.
运行结果:
解析:这里通过一个形象的例子来帮助理解CyclicBarrier。
- 一开始建立一个CyclicBarrier对象并设置parties的值为5(理解为开启一个游戏房间,且需要5个玩家都准备好的条件之后才能开启游戏,各个玩家才能进行游戏游玩)。
- 循环开启多个线程并各自调用barrier.await(),理解为玩家分别都进入房间并做好了准备,线程当调用barrier.await()方法计数会加1,如果计数还达不到CyclicBarrier预先设置的parties值时则该线程会进入等待状态。
- 当线程barrier.await()方法计数达到CyclicBarrier预先设置的parties值时,便开始游戏。这个时候所有的线程(玩家)便同时进入到了游戏中。
三、两者对比
最后
CyclicBarrier(同步屏障)和CountDownLatch(倒计时锁)都是不错的高并发编程工具类,两者很相似容易造成混淆,通过理解两者各自工作方式和特点并结合业务需求合理地应用他们会有不错的效益。