三、分析需求
先上个图,小伙伴们会更好理解
RunTask代表选手的跑完比赛的耗时,为了真实模拟,加了随机数,表示每个选手的耗时不一样。继续代码,直接在main方法中,进行比赛
上面是一些基础变量,记录耗时。小伙伴要注意要用AtomicLong原子类,避免线程安全问题。下面的代码就是比赛核心逻辑
1、创建线程(选手)
2、执行任务(赛跑)
3、记录成绩(耗时)
大会公布成绩
执行比赛
小伙伴看看,是不是明显不对啊,总耗时尽然为0,肯定有问题。
应该有人发现了,因为我们是在main方法中执行比赛的,其他线程单独执行,主main线程执行完就终止了程序,而不会管其他线程有没有结束。
这明显和我们想要的不一样,我们需要等所有的选手跑完,才能算比赛结束。那应该怎么优化呢?往下看
六、终点优化
根据上面的CyclicBarrier知识点,我们把代码优化一下
一、增加CyclicBarrier变量
//定义屏障,为什么要加1?final CyclicBarrier cb = new CyclicBarrier(nThreads + 1);
为什么要加1?因为比赛裁判肯定先到终点(即主线程),那也需要等待,所以屏障点需要加1。
注意:这个是根据业务来的,如果设置屏障点,是根据业务逻辑设计的
二、选手跑完到屏障点
在选手跑完后,增加到达屏障点,等待
三、裁判到屏障点
这个代码是在main主线程的,也就是裁判会先到,设置屏障点
终点优化结束,执行比赛吧
这个成绩应该没有问题,把大赛的成绩都正确的显示出来了。
//上面所有选手都已经开跑了//整个比赛的开始时间long startTime = System.currentTimeMillis();//。。。//整个比赛的结束时间long endTime = System.currentTimeMillis();
就是因为系统执行也是会消耗时间的。当然耗时不大就是几纳秒。小伙伴知道这个点后,会不会发现我们整个代码还存在一个问题?
八、起点问题
我们一场比赛是要等所有选手准备好后,等待裁判发令后,才能开跑。我们来看一下我们的选手开跑代码
选手是通过for循环创建出来的,而且创新好后,就执行start开跑了。这个是不对的。
小伙伴会说for循环很快的,没关系吧。
这里是很有关系的,创建选手耗时是比较长的,而且循环体也有耗时。我们看一下之前的系统耗时,就是获取结束时间也存在系统耗时,何况这里要分配内存、创建对象等。这样对其他选手就不公平了,那怎么办?老顾再分享一个并发控制类CountDownLatch
十、起点优化
增加变量,计数器为1,这个值是由我们的设计决定的
//增加CountDownLatch控制类final CountDownLatch cdl = new CountDownLatch(1);
选手预备等待
裁判发令
裁判发令后,所有的选手就会立即开跑,利用CountDownLatch达到了控制线程等待,一起执行。再执行比赛看看,也解决了系统耗时误差的问题
-----------大会公布成绩-------------比赛选手数:4------------------------所有选手总耗时:6686ms比赛执行完耗时:1920ms第一名耗时:1281ms最后一名耗时:1920ms
十一、总结
这篇文章只是个引子,把并发编程的两个重要的类抛出来,主要介绍应用场景。具体类的用法,小伙伴们可以网上自行学习。还有CyclicBarrier和CountDownLatch两者有相同点,有些场景可以替换使用。当然他们也有不同点,小伙伴们要注重关注。谢谢!!!
领取专属 10元无门槛券
私享最新 技术干货