田径运动会
点开这篇文章的你,突然感觉眼前一晃,我们已经穿越到了2008年的鸟巢,现在马上将要开始的项目的正是田径之王——百米短跑。
身为Coder的你突然想到,如果用代码来模拟赛跑,应该怎么实现呢?
于是你想到了使用线程来模拟每个运动员,每个线程都运行结束则表示选手都到达了终点,比赛结束。
那么,怎么来处理每个选手都完成比赛了呢。反正我是笨笨的想到了用 AtomicInteger
共享变量来记录已完成的选手数,等这个数等于选手数的时候就表示选手们已经全部完成比赛了。
但这个看起来是那么的不优雅。
重头戏(多唤醒一)
接下来重点来了。
CountDownLatch
—— Java AQS(AbstractQueuedSynchronizer) 中的一员大将前来报道。这个家伙可就是专门来处理上面这个情况的。
看下面这个代码:
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 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41
| import java.util.Random; import java.util.concurrent.CountDownLatch;
public class Test1 { private static final int TOTAL = 10;
public static void main(String[] args) throws InterruptedException { System.out.println("裁判员:比赛开始~ GO!"); CountDownLatch cdl = new CountDownLatch(TOTAL); for (int i = 1; i <= TOTAL; i++) { new Thread(new Task(cdl, i)).start(); } cdl.await(); System.out.println("裁判员:比赛结束!"); }
private static class Task implements Runnable { Task(CountDownLatch cdl, int idx) { this.cdl = cdl; this.idx = idx; }
private CountDownLatch cdl; private int idx;
@Override public void run() { try { Thread.sleep(new Random((long) (Math.random() * Integer.MAX_VALUE)).nextLong(1000 * 3)); System.out.printf("--[%d]选手%d准备好了%n", System.currentTimeMillis(), idx); Thread.sleep(new Random((long) (Math.random() * Integer.MAX_VALUE)).nextLong(1000 * 10)); } catch (InterruptedException e) { e.printStackTrace(); } System.out.printf("选手%d已到达终点!%n", idx); cdl.countDown(); } } }
|
上面的代码模拟了百米赛跑的场景,来让我们运行一下代码~
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
| 裁判员:比赛开始~ GO! --[1655357416735]选手1准备好了 --[1655357416899]选手9准备好了 --[1655357417090]选手4准备好了 --[1655357417174]选手7准备好了 --[1655357417737]选手8准备好了 选手8已到达终点! --[1655357418568]选手6准备好了 --[1655357418646]选手5准备好了 --[1655357418897]选手3准备好了 --[1655357419150]选手2准备好了 选手3已到达终点! --[1655357419343]选手10准备好了 选手7已到达终点! 选手6已到达终点! 选手1已到达终点! 选手9已到达终点! 选手5已到达终点! 选手4已到达终点! 选手10已到达终点! 选手2已到达终点! 裁判员:比赛结束!
|
欸~ 这个结果怎么看起来怪怪的(多线程问题,如果你的结果不是这种类似的,可以多运行几次)
改进(一唤醒多)
怎么会存在有的选手还没准备好,有的选手就已经到达终点了呢。
由于每个选手的准备时间不一样,那我们也应该在选手都准备好之后,才打响发令枪。
这样才和实际的情况比较吻合。OK,修改代码如下:
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 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48
| import java.util.Random; import java.util.concurrent.CountDownLatch;
public class Test2 { private static final int TOTAL = 10;
public static void main(String[] args) throws InterruptedException { System.out.println("裁判员:比赛开始~ READY!"); CountDownLatch single = new CountDownLatch(1); CountDownLatch cdl = new CountDownLatch(TOTAL); for (int i = 1; i <= TOTAL; i++) { new Thread(new Task(single, cdl, i)).start(); } Thread.sleep(1000 * 5); System.out.println("裁判员:GO!"); single.countDown(); cdl.await(); System.out.println("裁判员:比赛结束!"); }
private static class Task implements Runnable { Task(CountDownLatch single, CountDownLatch cdl, int idx) { this.single = single; this.cdl = cdl; this.idx = idx; }
private CountDownLatch single; private CountDownLatch cdl; private int idx;
@Override public void run() { try { Thread.sleep(new Random((long) (Math.random() * Integer.MAX_VALUE)).nextLong(1000 * 3)); System.out.printf("--[%d]选手%d准备好了%n", System.currentTimeMillis(), idx); single.await(); Thread.sleep(new Random((long) (Math.random() * Integer.MAX_VALUE)).nextLong(1000 * 10)); } catch (InterruptedException e) { e.printStackTrace(); } System.out.printf("选手%d已到达终点!%n", idx); cdl.countDown(); } } }
|
我们再次运行一下代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
| 裁判员:比赛开始~ READY! --[1655358774480]选手2准备好了 --[1655358774595]选手9准备好了 --[1655358774985]选手8准备好了 --[1655358775160]选手5准备好了 --[1655358775271]选手10准备好了 --[1655358775534]选手1准备好了 --[1655358775733]选手7准备好了 --[1655358776142]选手4准备好了 --[1655358776358]选手3准备好了 --[1655358777281]选手6准备好了 裁判员:GO! 选手3已到达终点! 选手4已到达终点! 选手6已到达终点! 选手8已到达终点! 选手7已到达终点! 选手10已到达终点! 选手5已到达终点! 选手1已到达终点! 选手2已到达终点! 选手9已到达终点! 裁判员:比赛结束!
|
这次的结果终于和预期一样了,不管你执行多少次代码模拟,也不会出现上面那种情况了。
OK,根据这个例子的两段代码 CountDownLatch
的使用场景就已经讲完了,至于原理吗? 这个就直接看源码去吧,这里就不多啰嗦啦!
最后
最后在提一下:
1 2 3 4 5 6 7
| public class CountDownLatch { public CountDownLatch(int count) { if (count < 0) throw new IllegalArgumentException("count < 0"); this.sync = new Sync(count); } }
|
上面这个构造方法中的 count
可简单理解为,调用countDown()
的次数达到这个值就会唤醒await,让阻塞的进程继续运行下去。
好了,本篇结束~ 撒花★,°:.☆( ̄▽ ̄)/$:.°★ 。
欢迎关注我的公众号 须弥零一,跟我一起学习IT知识。
如果您喜欢此博客或发现它对您有用,则欢迎对此发表评论。 也欢迎您共享此博客,以便更多人可以参与。 如果博客中使用的图像侵犯了您的版权,请与作者联系以将其删除。 谢谢 !