一场赛跑引起的并发知识,flutterrow 换行
RunTask 代表选手的跑完比赛的耗时,为了真实模拟,加了随机数,表示每个选手的耗时不一样。继续代码,直接在 main 方法中,进行比赛
上面是一些基础变量,记录耗时。小伙伴要注意要用 AtomicLong 原子类,避免线程安全问题。下面的代码就是比赛核心逻辑
1、创建线程(选手)2、执行任务(赛跑)3、记录成绩(耗时)
大会公布成绩
执行比赛
小伙伴看看,是不是明显不对啊,总耗时尽然为 0,肯定有问题。
应该有人发现了,因为我们是在 main 方法中执行比赛的,其他线程单独执行,主 main 线程执行完就终止了程序,而不会管其他线程有没有结束。这明显和我们想要的不一样,我们需要等所有的选手跑完,才能算比赛结束。那应该怎么优化呢?往下看
CyclicBarrier
我们这里引入一个知识点 CyclicBarrier 循环屏障,CyclicBarrier 是一组线程互相等待,只有全部到达屏障点以后才能继续执行。可以举个生活场景
大巴车进入服务区进行休息,大巴车是要等到所有乘客上车后,才能发车。并不是一个人上车了就可以发车了。这个是所有乘客都知道的规则,互相等待所有人上车,才发车。循环的意思就是大巴车是一直这种规则,可重复利用
我们比赛的例子正好匹配,不是一个选手到达终点(屏障)就比赛结束,而是要等到所有选手到达终点才能结束比赛。
终点优化
根据上面的 CyclicBarrier 知识点,我们把代码优化一下一、增加 CyclicBarrier 变量
//定义屏障,为什么要加 1?final CyclicBarrier cb = new CyclicBarrier(nThreads + 1);
为什么要加 1?因为比赛裁判肯定先到终点(即主线程),那也需要等待,所以屏障点需要加 1。
注意:这个是根据业务来的,如果设置屏障点,是根据业务逻辑设计的
二、选手跑完到屏障点
在选手跑完后,增加到达屏障点,等待三、裁判到屏障点
这个代码是在 main 主线程的,也就是裁判会先到,设置屏障点终点优化结束,执行比赛吧
这个成绩应该没有问题,把大赛的成绩都正确的显示出来了。
系统耗时
我们小伙伴再仔细观察下,上面的成绩:1、最后一名的耗时 3397ms2、比赛执行完耗时 3398ms 相差 1ms,当然我们这里设计是以毫秒为单位的,如果以纳秒为单位他们的相差会比 1ms 少。这个不是关键,关键是其实最后一名跑完,其实就是比赛结束了。按照道理比赛执行耗时和最后一名的耗时是一样的哦。比赛执行多次,效果都一样相差 1ms。这个是为什么呢?就是因为系统耗时,我们看看比赛是在什么时候记时的,是全部选手开跑后才记时的。这边就会存在误差,因为系统执行也会耗时
//上面所有选手都已经开跑了//整个比赛的开始时间 long startTime = System.currentTimeMillis();//。。。//整个比赛的结束时间 long endTime = System.currentTimeMillis();
就是因为系统执行也是会消耗时间的。当然耗时不大就是几纳秒。小伙伴知道这个点后,会不会发现我们整个代码还存在一个问题?
起点问题
我们一场比赛是要等所有选手准备好后,等待裁判发令后,才能开跑。我们来看一下我们的选手开跑代码![图片](https://uploader.shimo.im/f/KZsUdrd8ZAUamFYt.png!th
umbnail)选手是通过 for 循环创建出来的,而且创新好后,就执行 start 开跑了。这个是不对的。
小伙伴会说 for 循环很快的,没关系吧。
这里是很有关系的,创建选手耗时是比较长的,而且循环体也有耗时。我们看一下之前的系统耗时,就是获取结束时间也存在系统耗时,何况这里要分配内存、创建对象等。这样对其他选手就不公平了,那怎么办?老顾再分享一个并发控制类 CountDownLatch
CountDownLatch
CountDownLatch 是一个或一组线程等待其他线程完成各自的工作后再执行。举个例子:
大家考场考试,有人提前交卷,但监考老师是不能走的,因为还有人没有考完,只有等到所有人交卷了,老师才能走。是不是和 CyclicBarrier 类似,他们也有不同点,自行百度。
到我们这个案例中,应该要等待所有选手准备好后,才能开跑。
起点优化
增加变量,计数器为 1,这个值是由我们的设计决定的
//增加 CountDownLatch 控制类 final CountDownLatch cdl = new CountDownLatch(1);
选手预备等待
评论