CountDownLatch VS CyclicBarrier
案例分析
: 100 米短跑比赛, 运动员从赛前热身准备到比赛结束裁判宣布运动员排名
public class AnswerApp {
private static final DateTimeFormatter DATETIME_FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
// 比赛选手个数
private final static int COUNT = 8;
// 比赛最终排名
private static Queue<Integer> rank = new LinkedList<>();
private static CountDownLatch countDownLatch = new CountDownLatch(COUNT);
private static Random random = new Random();
public static void main(String[] args) {
CyclicBarrier cyclicBarrier = new CyclicBarrier(COUNT);
ExecutorService executorService = Executors.newFixedThreadPool(COUNT);
for (int i = 1; i <= COUNT; i++) {
final int n = i;
executorService.execute(() -> {
try {
// 模拟选手由于个人原因需要花费部分时间进行赛前热身
Thread.sleep(random.nextInt(5) * 1000);
// 等待所有任务准备就绪
System.out.println(MessageFormat.format("{0} 赛道[{1}]选手已准备就绪...", LocalDateTime.now().format(DATETIME_FORMATTER), n));
// 选手在起跑线前准备
cyclicBarrier.await();
// 只打印一条记录即可
if (n == 1) {
System.out.println();
System.out.println(MessageFormat.format("{0} 准备开始啦~~~倒数 3 2 1...", LocalDateTime.now().format(DATETIME_FORMATTER)));
System.out.println();
}
// 模拟所有选手都已经准备完毕, 倒数 3 2 1
Thread.sleep(3000);
System.out.println(MessageFormat.format("{0} 我是赛道[{1}]选手, 我开始跑啦", LocalDateTime.now().format(DATETIME_FORMATTER), n));
int time = random.nextInt(10) + 5;
// 只打印一条记录即可
if (n == 1) {
System.out.println(MessageFormat.format("\n{0} 选手们都在疯狂奔跑中...\n", LocalDateTime.now().format(DATETIME_FORMATTER)));
}
// 模拟选手的跑完全程的耗时
Thread.sleep(time * 1000);
System.out.println(MessageFormat.format("{0} 赛道[{1}]选手达到终点, 用时[{2}]s.", LocalDateTime.now().format(DATETIME_FORMATTER), n, time));
rank.add(n);
} catch (InterruptedException | BrokenBarrierException e) {
e.printStackTrace();
System.out.println(MessageFormat.format("{0} 选手[{1}]退出了比赛, 退赛原因[{2}]", LocalDateTime.now().format(DATETIME_FORMATTER), n, e.getMessage()));
} finally {
countDownLatch.countDown(); // 到达终点计数器减1
}
});
}
try {
// 阻塞, 直到所有线程都执行完毕, 即所有选手都跑完比赛(包括退赛)
countDownLatch.await();
System.out.println();
System.out.println(MessageFormat.format("{0} 最终排名[{1}]", LocalDateTime.now().format(DATETIME_FORMATTER), rank));
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
// 关闭线程池
executorService.shutdown();
}
}
}
-
CountDownLatch
: 用于记录选手达到终点的计数器, 计数器为0时, 所有选手都结束比赛(跑完比赛和退赛) -
CyclicBarrier
: 用于所有选手在起跑线相互等待, 等待所有选手都准备就绪, 再进行比赛[有重置功能, 调用reset()方法
]
程序运行结果
2019-07-06 11:46:18 赛道[2]选手已准备就绪...
2019-07-06 11:46:18 赛道[3]选手已准备就绪...
2019-07-06 11:46:19 赛道[4]选手已准备就绪...
2019-07-06 11:46:19 赛道[6]选手已准备就绪...
2019-07-06 11:46:20 赛道[7]选手已准备就绪...
2019-07-06 11:46:21 赛道[1]选手已准备就绪...
2019-07-06 11:46:21 赛道[5]选手已准备就绪...
2019-07-06 11:46:21 赛道[8]选手已准备就绪...
2019-07-06 11:46:21 准备开始啦~~~倒数 3 2 1...
2019-07-06 11:46:24 我是赛道[8]选手, 我开始跑啦
2019-07-06 11:46:24 我是赛道[2]选手, 我开始跑啦
2019-07-06 11:46:24 我是赛道[6]选手, 我开始跑啦
2019-07-06 11:46:24 我是赛道[3]选手, 我开始跑啦
2019-07-06 11:46:24 我是赛道[4]选手, 我开始跑啦
2019-07-06 11:46:24 我是赛道[7]选手, 我开始跑啦
2019-07-06 11:46:24 我是赛道[5]选手, 我开始跑啦
2019-07-06 11:46:24 我是赛道[1]选手, 我开始跑啦
2019-07-06 11:46:24 选手们都在疯狂奔跑中...
2019-07-06 11:46:31 赛道[1]选手达到终点, 用时[7]s.
2019-07-06 11:46:32 赛道[4]选手达到终点, 用时[8]s.
2019-07-06 11:46:32 赛道[5]选手达到终点, 用时[8]s.
2019-07-06 11:46:34 赛道[3]选手达到终点, 用时[10]s.
2019-07-06 11:46:37 赛道[2]选手达到终点, 用时[13]s.
2019-07-06 11:46:37 赛道[7]选手达到终点, 用时[13]s.
2019-07-06 11:46:38 赛道[8]选手达到终点, 用时[14]s.
2019-07-06 11:46:38 赛道[6]选手达到终点, 用时[14]s.
2019-07-06 11:46:38 最终排名[[1, 4, 5, 3, 2, 7, 8, 6]]
Semaphore
案例分析
: 一个工厂有 n-MACHINE 台机器, 但是有 n-WORKER 个工人, 一台机器同时只能被一个工人使用, 只有使用完了,其他工人才能继续使用
public class AnswerApp {
/** 工人数量 */
private static final int WORKER = 10;
/** 机器数量 */
private static final int MACHINE = 3;
private static final DateTimeFormatter DATETIME_FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
public static void main(String[] args) {
final Semaphore semaphore = new Semaphore(MACHINE);
ExecutorService executorService = Executors.newCachedThreadPool();
for (int i = 1; i <= WORKER; i++) {
final int index = i;
executorService.execute(() -> {
try {
// 工人获取一台机器用于作业的许可
semaphore.acquire();
System.out.println(MessageFormat.format("{0} 工作人员[{1}] take up a machine.",
LocalDateTime.now().format(DATETIME_FORMATTER), index));
Thread.sleep(5000);
System.out.println(MessageFormat.format("{0} 工作人员[{1}] release a machine.",
LocalDateTime.now().format(DATETIME_FORMATTER), index));
// 工厂生产完毕, 释放机器
semaphore.release();
} catch (InterruptedException e) {
e.printStackTrace();
}
});
}
executorService.shutdown();
}
}
程序运行结果
2019-07-06 13:42:40 工作人员[2] take up a machine.
2019-07-06 13:42:40 工作人员[1] take up a machine.
2019-07-06 13:42:40 工作人员[3] take up a machine.
2019-07-06 13:42:45 工作人员[3] release a machine.
2019-07-06 13:42:45 工作人员[2] release a machine.
2019-07-06 13:42:45 工作人员[1] release a machine.
2019-07-06 13:42:45 工作人员[4] take up a machine.
2019-07-06 13:42:45 工作人员[7] take up a machine.
2019-07-06 13:42:45 工作人员[5] take up a machine.
2019-07-06 13:42:50 工作人员[4] release a machine.
2019-07-06 13:42:50 工作人员[7] release a machine.
2019-07-06 13:42:50 工作人员[6] take up a machine.
2019-07-06 13:42:50 工作人员[8] take up a machine.
2019-07-06 13:42:50 工作人员[5] release a machine.
2019-07-06 13:42:50 工作人员[9] take up a machine.
2019-07-06 13:42:55 工作人员[8] release a machine.
2019-07-06 13:42:55 工作人员[9] release a machine.
2019-07-06 13:42:55 工作人员[6] release a machine.
2019-07-06 13:42:55 工作人员[10] take up a machine.
2019-07-06 13:43:00 工作人员[10] release a machine.
总结
-
CountDownLatch
: 一般用于某个线程A等待若干个其他线程执行完任务之后,它再执行
- 裁判等待所有的选手都跑完再进行公布排名
-
CyclicBarrier
: 一般用于一组线程互相等待至某个状态,然后这一组线程再同时执行
- 起跑准备时, 所有选手都相互等待, 等待所有人都准备好再一起开跑
- CountDownLatch是
不能够重用
的,而CyclicBarrier是可以重用
的(通过调用 reset 方法) -
Semaphore
: 其实和锁有点类似,它一般用于控制对某组资源的访问权限