- Fork-Join
-
Fork-Join(分而治之)
1)什么是分而治之?规模为N的问题,N<阀值时直接解决,N>阀值时,将N拆成M个并发执行
2)工作密取 -
用分支/合并框架执行并行求和
使用分支/合并框架的最佳做法对一个任务调用 join 方法会阻塞调用方,直到该任务做出结果。因此,有必要在两个子任务的计算都开始之后再调用它。否则,你得到的版本会比原始的顺序算法更慢更复杂,因为每个子任务都必须等待另一个子任务完成才能启动。不应该在 RecursiveTask 内部使用 ForkJoinPool 的 invoke 方法。相反,你应该始终直接调用 compute 或 fork 方法,只有顺序代码才应该用 invoke 来启动并行计算。对子任务调用 fork 方法可以把它排进 ForkJoinPool 。同时对左边和右边的子任务调用它似乎很自然,但这样做的效率要比直接对其中一个调用 compute 低。这样做你可以为其中一个子任务重用同一线程,从而避免在线程池中多分配一个任务造成的开销。调试使用分支/合并框架的并行计算可能有点棘手。特别是你平常都在你喜欢的IDE里面看栈跟踪(stack trace)来找问题,但放在分支?合并计算上就不行了,因为调用 compute的线程并不是概念上的调用方,后者是调用 fork 的那个。和并行流一样,你不应理所当然地认为在多核处理器上使用分支/合并框架就比顺序计算快。我们已经说过,一个任务可以分解成多个独立的子任务,才能让性能在并行化时有所提升。所有这些子任务的运行时间都应该比分出新任务所花的时间长;一个惯用方法是把输入/输出放在一个子任务里,计算放在另一个里,这样计算就可以和输入/输出同时进行。此外,在比较同一算法的顺序和并行版本的性能时还有别的因素要考虑。就像任何其他Java代码一样,分支/合并框架需要“预热”或者说要执行几遍才会被JIT编译器优化。这就是为什么在测量性能之前跑几遍程序很重要,我们的测试框架就是这么做的。同时还要知道,编译器内置的优化可能会为顺序版本带来一些优势(例如执行死码分析——删去从未被使用的计算)。对于分支/合并拆分策略还有最后一点补充:你必须选择一个标准,来决定任务是要进一步拆分还是已小到可以顺序求值。 -
Fork-Join的两个抽象子类
1)RecursiveTask:有返回值
2)RecursiveAction:没有返回值 -
调用方式
1)pool.invoke():同步调用
2)pool.execute():异步调用
-
- CountDownLatch
- 作用:是一个等待其他的线程完成工作以后执行,加强版join()
- await()用来等待,countDown计数器减一,当计数器为<=0时,解除等待
- 线程数和扣除点可以不一样,扣除点可以大于线程数
- CyclicBarrier
- 让一组线程到达某个屏障,被阻塞,一直到组内最后一个线程到达屏障时,屏障开放,所有被阻塞的线程继续执行
- CyclicBarrier(int parties, Runnable barrierAction):当屏障开放时barrierAction线程执行
- Semaphore信号量
- 控制同时访问某个特定资源的线程数量,用在流量控制,如连接池的设计
- Exchange
- 两个线程间的数据交换
- Callable,Future,FutureTask
- isDone结束,正常还是异常结束,或者自己取消,都返回true
- isCancelled 任务完成前被取消,返回true
- cancel(boolean) 尝试去终止任务;
1)任务还没开始,返回false
2)任务已经启动,cancel(true)中断正在运行的任务,中断成功返回true。cancel(false)不会去中断任务
3)任务已经结束,返回false - FutureTask是Future的实现类,构造器参数为Callable对象,相当于对Callable进行一次包装
网友评论