1. 问题描述
如题,现在接受到一个大任务,将这个任务交由计算机程序处理,计算密集型的,要求考虑适当的性能问题,并最终给出问题处理的报告。
注意,这个问题,描述中有几个非常重要的信息:
A. 是一个大任务 ---> 耗时不会短
B. 要由计算机程序处理 ---> 需要编写程序
C. 计算密集型 ---> 需要考虑任务划分策略
D. 最终给出问题处理报告 ---> 任务处理完前,是给不了的,这个报告动作只能在任务处理完后执行
2. 方案设计
结合问题描述,可以想到的是,我们可以采用多种方案:
1)、若有多个机器,可以将任务划分给多个机器一起处理,最终汇总,生成报告。这个是一个方案。
2)、若只有一台机器,我们最先要想到的是多线程进行处理,也可以用多进程。因为多进程涉及到进程上下文切换的问题,会有所影响性能,当然,不是主要的,要看是什么什么环境,这里,采用的是JAVA语言。可以考虑采用多线程方式,本案例将用多线程来解决这个问题。将大任务耗时长的作业,通过拆分,交由多线程进行处理,提高并发性。
3)、因为是计算密集型,所以,要重点考虑CPU时间片是否会被频繁的切换,频繁调度的话,也是会影响性能的;这里就采用和计算机核数一样多的线程数处理这个大任务。
4)、最终给出处理报告,必须要在任务处理完后,才做出报告,所以,程序设计的时候,要考虑报告事件是在多线程任务执行完毕后才执行的,这里就涉及到锁或者是等待同步问题。
综合上述分析过程,JAVA多线程实现,其实也有好多种方案,这里将重点讨论经典的两个JAVA多线程解决方案,分别是利用CountDownLatch和CyclicBarrier实现。
3. 代码实现
3.1 CountDownLatch方案
本方案,首先要搞清楚CountDownLatch的运行特点,他相当于是有一个同步点,通过倒计数的模式,每一个线程执行一次counDown(),促使CountDownLatch定义时指定的技术值减到0,即为满足到达同步点,此时,由CountDownLatch约束同步的后续事件可以进行。当然了,由CountDownLatch约束的任务,想要执行,必须得获取到了锁才行,即CountDownLatch的await()将会阻塞调用这个函数的线程T,直到线程T因为CountDownLatch的计数值减为0时获取锁后才能得以继续执行。
由此分析可以想到的是,依据CountDownLatch进行多任务同步约束,通常要设计两个CountDownLatch元素,一个相当于开关,另外一个相当于任务管理器,只有当开关打开,任务管理器里面的任务才开始运行,每一个任务执行完后,将任务管理器的计算值通过调用CountDownLatch的countDown()函数减1,直到计数器值为0,约束任务全部执行完后,即任务管理器CountDownLatch的await()阻塞解除,进入报告执行阶段。
下面是主程序:
package multiTask; import java.util.concurrent.CountDownLatch; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; /** * @author shihuc * @date 2018年6月22日 上午9:23:31 * * 此处的任务描述: * * 有一个大的活,正好,当前计算机配置也很不错,多核, 需要考虑计算性能,高效的处理完,处理完后,告知一下客户端,任务已经搞定。 * */ public class SolutionCDL { /** * @author shihuc * @param args */ public static void main(String[] args) { int cpuCore = Runtime.getRuntime().availableProcessors(); CountDownLatch switchLatch = new CountDownLatch(1); CountDownLatch taskLatch = new CountDownLatch(cpuCore); /* * 构建任务线程,依据当前系统的核数,构建相应数量的线程量 */ ExecutorService executor = Executors.newFixedThreadPool(cpuCore); for(int i=0; i<cpuCore; i++){ executor.submit(new WorkerCDL(switchLatch, taskLatch, "task" + i + " works hard...")); } executor.shutdown(); /* * 启动全局开关,让任务开始干活 */ switchLatch.countDown(); /* * 这里进行收尾工作,即当所有的worker线程完成任务后,进行的工作。本案例中,就是告知所有的活都搞定了 */ try { //所有的worker线程都结束后,即相应的countDown()函数被执行后,这里的await将会被解锁,进入后续任务。 taskLatch.await(); finalWork("CountDownLathc, Yeah, all the task is finished perfect..."); } catch (InterruptedException e) { e.printStackTrace(); } } private static void finalWork(String inf) { System.out.println(inf); } }
下面是任务程序:
package multiTask; import java.util.concurrent.CountDownLatch; /** * @author shihuc * @date 2018年6月22日 上午9:24:01 */ public class WorkerCDL implements Runnable { private CountDownLatch switcher; private CountDownLatch worker; private String info; public WorkerCDL(CountDownLatch s, CountDownLatch w, String info){ this.switcher = s; this.worker = w; this.info = info; } /* (non-Javadoc) * @see java.lang.Runnable#run() */ @Override public void run() { try { /* * 首先将干活的开关进行关闭,相当于设置一道统一的门,只有门打开的时候,才能进行后续的工作。 * 当线程运行到此处时,即遇到await时,处于阻塞状态,只有该门打开时,也就是开关管理器(CountDownLatch)计数器降到0了,才会开门,释放锁。 */ switcher.await(); /* * 这里相当于具体的干活逻辑模块 */ doWork(info); /* * 活干完了,当前任务管理器(CountDownLatch的实例worker)要将计数器减1。 * 主任务管理器将依据此处的任务管理器是否全部完成来决定能否进入后续的逻辑。 */ worker.countDown(); } catch (InterruptedException e) { e.printStackTrace(); } } private void doWork(String s) { //模拟这个任务耗时比较长时间,这里固定耗时2s try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(s); } }