把大任务分割成若干个小任务,最终汇总每个小任务结果后得到大任务结果的框架。
工作窃取算法
某个线程从其他队列里窃取任务来执行。使用的场景是一个大任务拆分成多个小任务,为了减少线程间的竞争,把这些子任务分别放到不同的队列中,并且每个队列都有单独的线程来执行队列里的任务,线程和队列一一对应。
然而依然会出现一种情况:线程A先把队列里的工作搞定了,而B还有一些任务,A帮B做任务,但如果两个线程访问同一个队列,会产生竞争,所以A想了一个办法,从双端队列的尾部拿任务执行。而B线程永远是从双端队列的头部拿任务执行(任务是一个个独立的小任务)。
- 优点:
利用了线程进行并行计算,减少了线程间的竞争。
- 缺点:
- 如果双端队列中只有一个任务时,线程间会存在竞争。
- 消耗了更多的系统资源,如会创建多个线程和多个双端队列。
框架设计
-
分割任务
使用fork类把大任务分割为小任务
-
执行任务合并结果
分割的子任务分别放在双端队列里,启动线程从双端队列获取任务并执行。执行结果放在同一的一个队列,启动一个线程从队列里拿数据,然后合并。
使用2个类完成以上事务:
- ForkJoinTask:提供在任务中执行fork和join操作的机制。一般情况下,我们并不需要直接继承ForkJoinTask类,只需要继承它的子类,它的子类有两个:
- RecursiveAction:用于没有返回结果的任务。
- RecursiveTask:用于有返回结果的任务。
- ForkJoinPool:任务ForkJoinTask需要通过ForkJoinPool来执行。
参见代码CountTask
异常处理
使用isCompletedAbnormally()检查任务是否已经抛出异常或者被取消,并且可以通过ForkJoinTask的getException()获取异常
实现原理
-
fork
调用pushTask方法异步执行任务,然后返回,pushTask方法将当前任务存放在ForkJoinTask数组队列里,然后再调用ForkJoinPool的signalWork()唤醒或创建一个工作线程来执行任务
-
join
主要作用是阻塞当前线程并等待获取结果,当中会调用doJoin()判断当前任务的状态