JAVA并发编程Fork\Join分而治之--RecursiveTask(有返回值)和RecursiveAction(无返回值)

fork/join 分而治之
1.什么是Fork/join框架?
从JDK1.7开始,Java提供Fork/Join框架用于并行执行任务。它的思想就是讲一个大任务分割成若干小任务,最终汇总每个小任务的结果得到这个大任务的结果。如下图:

分而治之思想
2.什么是分而治之思想
可以简单的理解为:将规模为N的问题,当N<阈值,直接解决;当N>阈值,将N分解为K个小规模子问题,子问题互相对立,与原问题形式相同,将子问题的解合并得到原问题的解。

3. 工作窃取(work-stealing)


假如我们需要做一个比较大的任务,我们可以把这个任务分割为若干互不依赖的子任务,为了减少线程间的竞争,于是把这些子任务分别放到不同的队列里,并为每个队列创建一个单独的线程来执行队列里的任务,线程和队列一一对应,比如A线程负责处理A队列里的任务。如果现场A的任务执行完成后,于是它就去其他线程B的队列里窃取一个任务来执行。而在这时它们会访问同一个队列,所以为了减少窃取任务线程和被窃取任务线程之间的竞争,通常会使用双端队列,被窃取任务线程永远从双端队列的头部拿任务执行,而窃取任务的线程永远从双端队列的尾部拿任务执行,也就是线程B从头部拿取,而线程A从尾部获取任务开始执行。

工作窃取算法的优点是充分利用线程进行并行计算,并减少了线程间的竞争,其缺点是在某些情况下还是存在竞争,比如双端队列里只有一个任务时。并且消耗了更多的系统资源,比如创建多个线程和多个双端队列。

4.如何实现Fork/join
java 的ForkJoinTask 抽象类中提供compute方法给我们实现这种思想。java又提供两个抽象类继承ForkJoinTask ,分别是RecursiveAction和RecursiveTask.他们的区别在于:RecursiveTask有返回值,RecursiveAction无返回值。

4.1 fork/join标准规范

在这里插入图片描述
4.2 RecursiveTask的有返回值实现:
接下来我们以这样的例子,Fork/Join的同步用法实现有返回结果值:统计整形数组中所有元素的和


//RecursiveTask有返回值
public class FaceCompareTask extends RecursiveTask<FaceFeature> {

    private static final Logger logger = LoggerFactory.getLogger(FaceCompareTask.class);

    private final Spliterator<FaceFeature> spliterator;
    //是否找到标识
    private final AtomicBoolean flag;
    //识别目标
    private final FaceFeature target;
    private final FaceEngine engine;
    private final float score;

    public FaceCompareTask(Spliterator<FaceFeature> spliterator, AtomicBoolean flag, FaceFeature target, FaceEngine engine, float score) {
        this.spliterator = spliterator;
        this.flag = flag;
        this.target = target;
        this.engine = engine;
        this.score = score;
    }

    @Override
    protected FaceFeature compute() {
        long listSize = spliterator.estimateSize();
        //数据分段
        if (listSize > 100) {
            FaceCompareTask left = new FaceCompareTask(this.spliterator, flag, target, engine, score);
            FaceCompareTask right = new FaceCompareTask(this.spliterator.trySplit(), flag, target, engine, score);
            invokeAll(left,right);
            FaceFeature leftResult = left.join();
            FaceFeature rightResult = right.join();
            return leftResult != null ? leftResult : rightResult;
        } else {
            List<FaceFeature> list = new ArrayList<>();
            spliterator.forEachRemaining(list::add);
            FaceFeature result = null;
            for (FaceFeature faceFeature : list) {
                //其他线程已找到
                if (flag.get()) {
                    break;
                }
                FaceSimilar similar = new FaceSimilar();
                int code = engine.compareFaceFeature(faceFeature, target, similar);
                /*if (code != 0 && logger.isDebugEnabled()) {
                    logger.debug("compare match error:" + code);
                }*/
                //找到后,将标识设为true
                if (similar.getScore() >= score) {
                    flag.set(true);
                    result = faceFeature;
                    break;
                }
            }
            return result;
        }
    }
}


4.2 RecursiveAction的无返回值实现:
例子是Fork/Join的异步用法不要求返回值,控制台打印结果:遍历指定目录(含子目录)寻找指定txt结尾的文件

public class RecursiveTaskTest {

    public static void main(String[] args)throws Exception{
        // 创建包含Runtime.getRuntime().availableProcessors()返回值作为个数的并行线程的ForkJoinPool
        ForkJoinPool forkJoinPool = new ForkJoinPool();
        List<Integer> handlerList = new ArrayList<>();
        for(int i=1;i<=1000;i++){
            handlerList.add(i);
        }
        // 提交可分解的PrintTask任务
        ForkJoinTask<Void> submit = forkJoinPool.submit(new RaskDemo(handlerList.spliterator()));

        //阻塞当前线程直到 ForkJoinPool 中所有的任务都执行结束
        submit.get();//获取结果:一直等待(相当于主线程阻塞),直到能获取到子线程执行的结果。
        // 关闭线程池
        //forkJoinPool.shutdown();

    }

}

class RaskDemo extends RecursiveAction {
    /**
     * 每个"小任务"最多只打印20个数
     */
    private static final int MAX = 20;

    private Spliterator<Integer> spliterator;

    public RaskDemo(Spliterator<Integer> spliterator) {
        this.spliterator = spliterator;
    }

    @Override
    protected void compute() {
        long leastSize = spliterator.estimateSize();
        //当end-start的值小于MAX时,开始打印
        if (leastSize < MAX) {
            List<Integer> list = new ArrayList<>();
            spliterator.forEachRemaining(list::add);
            list.forEach(integer -> {
                System.out.println(Thread.currentThread().getName() + "i的值" + integer);
            });
        } else {
            RaskDemo left = new RaskDemo(spliterator);
            RaskDemo right = new RaskDemo(spliterator.trySplit());
            left.fork();
            right.fork();
        }
    }
}
}

1,invokeAll(task)方法,主动执行其它的ForkJoinTask,并等待Task完成。(同步的)

2,fork方法,让一个task执行(异步的)

3,join方法,让一个task执行(同步的,它和fork不同点是同步或者异步的区别)

4,可以使用join来取得ForkJoinTask的返回值。由于RecursiveTask类实现了Future接口,所以也可以使用get()取得返回值。 
get()和join()有两个主要的区别: 
join()方法不能被中断。如果你中断调用join()方法的线程,这个方法将抛出InterruptedException异常。 
如果任务抛出任何未受检异常,get()方法将返回一个ExecutionException异常,而join()方法将返回一个RuntimeException异常。

5,ForkJoinTask在不显示使用ForkJoinPool.execute/invoke/submit()方法进行执行的情况下,也可以使用自己的fork/invoke方法进行执行。 
使用fork/invoke方法执行时,其实原理也是在ForkJoinPool里执行,只不过使用的是一个“在ForkJoinPool内部生成的静态的”ForkJoinPool。

6,ForkJoinTask有两个子类,RecursiveAction和RecursiveTask。他们之间的区别是,RecursiveAction没有返回值,RecursiveTask有返回值。

7,看看ForkjoinTask的Complete方法的使用场景 
这个方法好要是用来使一个任务结束。这个方法被用在结束异步任务上,或者为那些能不正常结束的任务,提供一个选择。

8,Task的completeExceptionally方法是怎么回事。 
这个方法被用来,在异步的Task中产生一个exception,或者强制结束那些“不会结束”的任务 
这个方法是在Task想要“自己结束自己”时,可以被使用。而cancel方法,被设计成被其它TASK调用。 
当你在一个任务中抛出一个未检查异常时,它也影响到它的父任务(把它提交到ForkJoinPool类的任务)和父任务的父任务,以此类推。

9,可以使用ForkJoinPool

.execute(异步,不返回结果)、

invoke(同步,返回结果)、

submit(异步,返回结果)方法,来执行ForkJoinTask。

10,ForkJoinPool有一个方法commonPool(),这个方法返回一个ForkJoinPool内部声明的静态ForkJoinPool实例。 在jdk1.8里面才有。文档上说,这个方法适用于大多数的应用。这个静态实例的初始线程数,为“CPU核数-1 ”(Runtime.getRuntime().availableProcessors() - 1)。 
 

参考

1、https://blog.csdn.net/LCBUSHIHAHA/article/details/104449454?utm_medium=distribute.pc_relevant_t0.none-task-blog-2%7Edefault%7EBlogCommendFromMachineLearnPai2%7Edefault-1.control&dist_request_id=&depth_1-utm_source=distribute.pc_relevant_t0.none-task-blog-2%7Edefault%7EBlogCommendFromMachineLearnPai2%7Edefault-1.control

2、https://www.jianshu.com/p/32a15ef2f1bf

3、https://www.jianshu.com/p/de025df55363

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值