春时享受花红草绿,冬时欣赏冰雪风霜
晴天时爱晴,雨天时爱雨
日常工作中,经常会碰到这样的场景:有时候数据量特别大,任务量特别多,我们通常会开启多线程去分批执行任务,在所有任务执行完了之后,再去执行接下来的作业。
这时候,会想到 java 并发包提供的基础工具类,其中 有 CountDownLatch 和 CyclicBarrier ,它们都是提供多线程环境的协调功能,但是具体有什么区别呢?
CountDownLatch 操作的是事件,阻塞足够多的次数即可,不管几个线程;而 CyclicBarrier 侧重点是线程,强调多个线程间互相等待,同时结束。
01
CountDownLatch 用法
从代码层面,CountDownLatch 的用法是:
// 设置10个计数
CountDownLatch countDownLatch = new CountDownLatch(10);
// 每次调用即可减1
countDownLatch.countDown();
// 其他线程一直等待减到0后,才继续执行
countDownLatch.await()
从一个场景出发:
现在有订单库和派送单库,先查询订单,再查询派送单,之后对比订单和派送单,将差异写入差异库。
其中查询订单库和查询派送单库,可以用两个线程并行执行。都执行完了之后,才执行对账。
我们可以写一下伪代码:
Executor executor = Executors.newFixedThreadPool(2);
while(存在未对账订单) {
// 计数器初始化为2
CountDownLatch latch = new CountDownLatch(2);
// 查询未对账订单
executor.execute(() -> {
pOrders = getPOrders();
latch.countDown();
});
// 查询派送单
executor.execute(()-> {
dOrders = getDOrders();
latch.countDown();
});
// 等待两个查询结束
latch.await();
// 执行对账操作
diff = check(pOrders,dOrders);
// 差异写入差异库
save(diff);
}
我们使用只有2个核心线程的线程池,分别执行查询订单和派送单的操作,并且初始化了一个大小为 2 的 CountDownLatch,每次查询完后,都要 countDown();
主线程则一直等待减为 0 了之后,才开始继续往下执行。
02
CyclicBarrier 用法
从代码使用角度来说:
// 初始化值为5的栅栏
CyclicBarrier cyclicBarrier = new CyclicBarrier(5);
// 每个线程调用 await()
cyclicBarrier.await();
// 等到有 5 个线程都执行了 await() 之后,继续执行。
// 并且 栅栏的 计数器会自动重置为 5 ,可以接着用
然后我们模拟一个场景
在英雄联盟中,选好英雄之后,会等待所有 10 个玩家进度条都到 100% 才开始游戏,我们可以使用 CyclicBarrier 来模拟这个场景
public class CyclicBarrierTest {
private final static ExecutorService EXECUTOR_SERVICE = Executors.newFixedThreadPool(5);
private final static CyclicBarrier BARRIER = new CyclicBarrier(10);
public static void main(String[] args) {
for (int i = 0; i < 10; i++) {
final String name = "玩家" + i;
EXECUTOR_SERVICE.execute(new Runnable() {
@Override
public void run() {
try {
Thread.sleep(2000);
System.out.println(name + "已准备,等待其他玩家准备...");
BARRIER.await();
Thread.sleep(1000);
System.out.println(name + "已加入游戏");
} catch (InterruptedException e) {
System.out.println(name + "离开游戏");
} catch (BrokenBarrierException e) {
System.out.println(name + "离开游戏");
}
}
});
}
EXECUTOR_SERVICE.shutdown();
}
}
最后,比较一下 CountDownLatch 和 CyclicBarrier 的不同点:
CountDownLatch 是不可以重置的,所以无法重用;而 CyclicBarrier 则没有这个限制,可以重用;
CountDownLatch 的基本操作组合是 countDown/await。调用 await 的线程阻塞等待 countDown 足够多的次数,不管你是在一个线程还是多个线程里 countDown,只要次数足够即可。
CyclicBarrier 的基本操作组合,则就是 await,当所有伙伴 (parties)都调用了 await,才会继续进行任务,并自动进行重置。
注意,正常情况下,CyclicBarrier 的重置都是自动发生的,如果我们调用 reset 方法,但还有线程在等待,就会导致等待线程被打扰,抛出 BrokenBarrierException 异常。
CyclicBarrier 侧重点是线程,而不是调用事件,它的典型应用场景是用来等待并发线程结束。
下次见 -.-