Fork-Join 分治编程

在JDKl.7 版本中提供了 Fork/Join 并行执行任务框架,它的主要作用是把大任务分割成若干个小任务,再对每个小任务得到的结果进行汇总, 此种开发方法也叫分治编程,分治编程可以极大地利用 CPU 资源,提高任务执行的效率,也是目前与多线程有关的前沿技术。

Fork-Join 分治编程与类结构

JDK 中并行执行框架 Fork-Join 使用了“工作窃取(work-stealing)”算法,它是指某个线程从其他队列里窃取任务来执行,那这样做有什么优势或者目的是什么呢?比如要完成一个比较大的任务,完全可以把这个大的任务分割为若干互不依赖的子任务/小任务,为了更加方便地管理这些任务,于是把这些子任务分别放到不同的队列里,这时就会出现有的线程会先把自己队列里的任务快速执行完毕,而其他线程对应的队列里还有任务等待处理,完成任务的线程与其等着,不如去帮助其他线程分担要执行的任务,于是它就去其他线程的队列里窃取一个任务来执行,这就是所谓的“工作窃取(work-stealing )”算法。JDKl.7 中实现分治编程需要使用 ForkJoinPool 类,此类的主要作用是创建一个任务池。
Fork-join框架的运行流程

ForkJoinPool 继承关系

class ForkJoinPool extends AbstractExecutorService
abstract classAbstractExecutorService implements ExecutorService
interface ExecutorService extends Executor
1.类ForkJoinPool提供的功能是一个任务池,而执行具体任务却不是 ForkJoinPool,而是ForkJoinTask 类
2.ForkJoinTask是抽象类,不能实例化,所以需要该类的3个子类 CountedCompleter、RecursiveAction、RecursiveTask来实现具体的功能。
3.RecursiveAction:用于没有返回结果的任务;RecursiveTask:用于有返回结果的任务

ForkJoinPool 实现求和实验

使用RecursiveTask,子任务返回求和,父任务join获取,最终最顶层任务返回总和。

1-9求和示例

public class ForkJoinDemo {
    public static class MyRecursiveTask extends RecursiveTask<Integer> {
        //拆分任务的阀值
        private int threshold = 1;
        //开始位置
        private int startIndex;
        //结束位置
        private int endIndex;

        public MyRecursiveTask(int startIndex, int endIndex) {
            this.startIndex = startIndex;
            this.endIndex = endIndex;
        }

        @Override
        protected Integer compute(){
            if (endIndex - startIndex > threshold) {
                //拆分任务
                int midIndex = (endIndex + startIndex)/2;
                //拆分为2个子任务
                MyRecursiveTask left = new MyRecursiveTask(startIndex,midIndex);
                MyRecursiveTask right = new MyRecursiveTask(midIndex+1,endIndex);
                //执行两个子任务
                this.invokeAll(left,right);
                Integer leftValue = 0;
                Integer rightValue = 0;
                //获取子任务所得的和,join方法堵塞,可将异常抛出。get()方法,异常必须处理
                leftValue = left.join();
                rightValue = right.join();
                //返回拆分后两个子任务求和之和
                return leftValue + rightValue;
            } else {
                //不需要拆分直接返回计算和
                if (endIndex == startIndex) {
                    return endIndex;
                } else {
                    return endIndex + startIndex;
                }
            }
        }
    }


    public static void main(String[] args) {
        ForkJoinPool forkJoinPool = null;
        try {
            forkJoinPool = new ForkJoinPool();
            //提交任务
            ForkJoinTask task = forkJoinPool.submit(new MyRecursiveTask(1,9));
            //获取最终的和,输出
            System.out.println(task.join());
        } catch (Exception e) {
            System.out.println("main线程捕获了异常!");
            e.printStackTrace();
        }
    }
}

45

ForkJoinPool类execute()方法

1.void execute(ForkJoinTask<?> task)
2.void execute(Runnable task)
3.execute方法参数支持ForkJoinTask和Runnable ,但是无返回值。如果获取任务的结果,ForkJoinTask可以通过自身join()或get()获取,Runnable 不行。
示例

public class ForkJoinDemo {
    public static class MyRecursiveTask extends RecursiveTask<Integer> {
        //拆分任务的阀值
        private int threshold = 1;
        //开始位置
        private int startIndex;
        //结束位置
        private int endIndex;

        public MyRecursiveTask(int startIndex, int endIndex) {
            this.startIndex = startIndex;
            this.endIndex = endIndex;
        }

        @Override
        protected Integer compute(){
            if (endIndex - startIndex > threshold) {
                //拆分任务
                int midIndex = (endIndex + startIndex)/2;
                //拆分为2个子任务
                MyRecursiveTask left = new MyRecursiveTask(startIndex,midIndex);
                MyRecursiveTask right = new MyRecursiveTask(midIndex+1,endIndex);
                //执行两个子任务
                this.invokeAll(left,right);
                Integer leftValue = 0;
                Integer rightValue = 0;
                //获取子任务所得的和,join方法堵塞,可将异常抛出。get()方法,异常必须处理
                leftValue = left.join();
                rightValue = right.join();
                //返回拆分后两个子任务求和之和
                return leftValue + rightValue;
            } else {
                //不需要拆分直接返回计算和
                if (endIndex == startIndex) {
                    return endIndex;
                } else {
                    return endIndex + startIndex;
                }
            }
        }
    }


    public static void main(String[] args) {
        ForkJoinPool forkJoinPool = null;
        try {
            forkJoinPool = new ForkJoinPool();
            MyRecursiveTask myRecursiveTask =  new MyRecursiveTask(1,9);
            /**************************execute提交任务*************************************/
            forkJoinPool.execute(myRecursiveTask);
            //通过myRecursiveTask.join()获取最终的和,输出
            System.out.println(myRecursiveTask.join());
            //通过myRecursiveTask.get()获取最终的和,输出
            System.out.println(myRecursiveTask.get());
        } catch (Exception e) {
            System.out.println("main线程捕获了异常!");
            e.printStackTrace();
        }
    }
}

45
45

ForkJoinPool类submit()方法

1.ForkJoinTask submit(ForkJoinTask task),有返回值,get(),join()方法获取返回值,会堵塞
2.ForkJoinTask submit(Callable task),有返回值,get(),join()方法获取返回值,会堵塞
3.ForkJoinTask submit(Runnable task, T result), Runnable 参数不会返回值,但是可以将result作为参数传进去,利用Runnable 对参数result进行修改,这样主方法获取到的 result就是最新的,使用这种方式产生返回值。
4.ForkJoinTask<?> submit(Runnable task) ,传人 Runnable 接口没有返回值,但如果调用 get()方法依然呈阻塞状态,等待 Runnable 结束再返回null。

方法 public List<Future<*T>>invokeAll(Collection<? extends Callable<*T>> tasks )的使用

 public <T> List<Future<T>> invokeAll(Collection<? extends Callable<T>> tasks) 方法有堵塞性,会一直等到任务执行结束再执行下面的代码,
 任务异步执行。 
public class ForkJoinDemo {
    public static class MyCallable implements Callable<String>{
        String name;
        public MyCallable(String name) {
            super();
            this.name = name;
        }

        @Override
        public String call() throws Exception{
            //模拟任务执行时间
            Thread.sleep(1000);
            System.out.println(name+"执行完毕");
            return Thread.currentThread().getName()+"执行完毕";
        }
    }


    public static void main(String[] args) {
        ForkJoinPool forkJoinPool = null;
        try {
            forkJoinPool = new ForkJoinPool();
            List<MyCallable> list = new ArrayList<>();
            list.add(new MyCallable("任务1"));
            list.add(new MyCallable("任务2"));
            list.add(new MyCallable("任务3"));
            System.out.println("开始提交任务" + System.currentTimeMillis());
            //提交任务
            List<Future<String>> futures = forkJoinPool.invokeAll(list);
            System.out.println("结束提交任务" + System.currentTimeMillis());
            //获取返回值
            for(Future<String>  future: futures){
                System.out.println("返回值:" +future.get()+",时间:" + System.currentTimeMillis());
            }
        } catch (Exception e) {
            System.out.println("main线程捕获了异常!");
            e.printStackTrace();
        }
    }
}

开始提交任务1596988839203
任务3执行完毕
任务1执行完毕
任务2执行完毕
结束提交任务1596988840206
返回值:ForkJoinPool-1-worker-19执行完毕,时间:1596988840206
返回值:ForkJoinPool-1-worker-5执行完毕,时间:1596988840206
返回值:ForkJoinPool-1-worker-23执行完毕,时间:1596988840206

方法 public void shutdown()的使用

1.shutdown()方法执行完之后,已经提交子任务不会结束,子任务中断状态也没有改变,不会对sleep等产生影响,对于主任务也不会立即结束,而是等待主任务执行完毕。
2.shutdown()方法执行之后,提交新任务时会出错,抛异常RejectedExecutionException。

public class ForkJoinDemo {
    public static class MyCallable implements Callable<String>{
        String name;
        public MyCallable(String name) {
            super();
            this.name = name;
        }

        @Override
        public String call() throws Exception{
            //模拟任务执行时间
            Thread.sleep(1000);
            System.out.println(name+"执行完毕");
            return Thread.currentThread().getName()+"执行完毕";
        }
    }


    public static void main(String[] args) {
        ForkJoinPool forkJoinPool = null;
        try {
            forkJoinPool = new ForkJoinPool();
            Future<String> future = forkJoinPool.submit(new MyCallable("任务1"));
            //执行shutdown()中断
            forkJoinPool.shutdown();
            //执行下面的代码会抛异常RejectedExecutionException
            //Future<String> futureA = forkJoinPool.submit(new MyCallable("任务1"));
            System.out.println("返回值:" +future.get()+",时间:" + System.currentTimeMillis());
        } catch (Exception e) {
            System.out.println("main线程捕获了异常!");
            e.printStackTrace();
        }
    }
}

任务1执行完毕
返回值:ForkJoinPool-1-worker-19执行完毕,时间:1596989475097

子任务MyCallable中有sleep,但是没有抛异常,说明shutdown()方法,没有更改其中断状态isInterrupted为false。主任务也正常结束。但是提交新任务会抛异常。

方法 public List<*Runnable> shutdownNow() 的使用

1.执行shutdownNow()方法,会将子任务的中断状态置为true,需要配合(Thread.currentThread() .isinterrupted() == true)使用,否则会正常执行结束。
2.主任务会正常执行结束,但是不能再提交任务,因为池已经关闭,会抛出RejectedExecutionException异常。
3.与shutdown()不同,执行shutdownNow()方法之后,主任务调用get()方法尝试获取返回值时会出错抛异常CancellationException。

在这里插入图片描述

方法 isTerminating()和 isTerminated()的使用

1.关于shutdown()方法:

1.执行完shutdown()之后,isTerminating()方法永远返回false,执行之前也返回false。当子任务执行结束后isTerminated()方法会返回true

示例

public class ForkJoinDemo {
    public static class MyCallable implements Callable<String>{
        String name;
        public MyCallable(String name) {
            super();
            this.name = name;
        }

        @Override
        public String call() throws Exception{
            //模拟任务执行时间
            for(int i = 0; i< Integer.MAX_VALUE/100;i++){
                Math.random();
            }
            System.out.println(name+"执行完毕");
            return Thread.currentThread().getName()+"执行完毕";
        }
    }


    public static void main(String[] args) {
        ForkJoinPool forkJoinPool = null;
        try {
            forkJoinPool = new ForkJoinPool();
            Future<String> future = forkJoinPool.submit(new MyCallable("任务1"));
            //执行shutdown()中断
            System.out.println(forkJoinPool.isTerminating());
            forkJoinPool.shutdown();
            System.out.println(forkJoinPool.isTerminating());
            Thread.sleep(2000);
            System.out.println(forkJoinPool.isTerminating());
            System.out.println(forkJoinPool.isTerminated());
        } catch (Exception e) {
            System.out.println("main线程捕获了异常!");
            e.printStackTrace();
        }
    }
}

false
false
任务1执行完毕
false
true

1.关于shutdownNow()方法:

1.执行完shutdownNow()之后,isTerminating()方法返回true,执行之前返回false,当子任务执行结束后isTerminated()方法会返回true,isTerminating返回false.

示例

public class ForkJoinDemo {
    public static class MyCallable implements Callable<String>{
        String name;
        public MyCallable(String name) {
            super();
            this.name = name;
        }

        @Override
        public String call() throws Exception{
            //模拟任务执行时间
            for(int i = 0; i< Integer.MAX_VALUE/100;i++){
                Math.random();
            }
            System.out.println(name+"执行完毕");
            return Thread.currentThread().getName()+"执行完毕";
        }
    }


    public static void main(String[] args) {
        ForkJoinPool forkJoinPool = null;
        try {
            forkJoinPool = new ForkJoinPool();
            Future<String> future = forkJoinPool.submit(new MyCallable("任务1"));
            //执行shutdown()中断
            System.out.println(forkJoinPool.isTerminating());
            forkJoinPool.shutdownNow();
            System.out.println(forkJoinPool.isTerminating());
            Thread.sleep(2000);
            System.out.println(forkJoinPool.isTerminating());
            System.out.println(forkJoinPool.isTerminated());
        } catch (Exception e) {
            System.out.println("main线程捕获了异常!");
            e.printStackTrace();
        }
    }
}

false
true
任务1执行完毕
false
true

方法public boolean isShutdown()的使用

执行了shutdown()或者shutdown()方法,isShutdown()会返回true。

方法public boolean awaitTermination(long timeout, TimeUnit unit)的使用

方法 awaitTermination(long timeout, TimeUnit unit)的作用是等待池被销毁的最长时间,具有堵塞特性,需要配合shutdown()或者shutdownNow()使用,如果线程池销毁返回true,否则返回false.
示例

public class ForkJoinDemo {
    public static class MyCallable implements Callable<String>{
        String name;
        public MyCallable(String name) {
            super();
            this.name = name;
        }

        @Override
        public String call() throws Exception{
            //模拟任务执行时间
            Thread.sleep(2000);
            System.out.println(name+"执行完毕");
            return Thread.currentThread().getName()+"执行完毕";
        }
    }


    public static void main(String[] args) {
        ForkJoinPool forkJoinPool = null;
        try {
            forkJoinPool = new ForkJoinPool();
            Future<String> future = forkJoinPool.submit(new MyCallable("任务1"));
            //执行shutdown()中断
            forkJoinPool.shutdown();
            System.out.println("开始等待池结束"+System.currentTimeMillis());
            //子任务2s之后结束
            System.out.println(forkJoinPool.awaitTermination(3000,TimeUnit.SECONDS));
            System.out.println("池已经结束"+System.currentTimeMillis());
        } catch (Exception e) {
            System.out.println("main线程捕获了异常!");
            e.printStackTrace();
        }
    }
}

开始等待池结束1596991728529
任务1执行完毕
true
池已经结束1596991730529

awaitTermination(3000,TimeUnit.SECONDS)最毒堵塞3s,堵塞2s之后,子任务结束,池关闭,awaitTermination不在等待返回true表示池结束。

方法 execute(task),submit(task),以及 invoke(task)区别

方法 execute(task),submit(task),以及 invoke(task)都可以在异步队列中执行任务,需要注意的是,方法 invoke()是阻塞的,而它们在使用上的区别其实很简单, execute(task)只执行任务,没有返回值,而 submit(task)方法有返回值,返回值类型是 ForkJoinTask ,想取得返回值时,需要使用 ForkJoinTask 对象的 get()方法,而 invoke(task)和 submit(task)方法
一样都具有返回值的功能,区别就是 invoke(task)方法直接将返回值进行返回,而不是通过ForkJoinTask 对象的 get()方法。

监视 pool 池的状态

类提供了若干个方法来监视任务池的状态:
★方法 getParallelism ():获得并行的数量,与 CPU 的内核数有关;
★方法 getPoolSize ():获得任务池的大小;
★方法getQueuedSubmissionCount():取得已经提交但尚未被执行的任务数
★方法 basQueuedSubmissions():判断队列中是否有未执行的任务;
★方法 getActiveThreadCount():获得活动的线程个数;
★方法 getQueuedTaskCount():获得任务的总个数;
★方法 getStealCount():获得偷窃的任务个数;
★方法 getRunningThreadCount():获得正在运行并且不在阻塞状态下的线程个数;
★方法 isQuiescent():判断任务池是否是静止未执行任务的状态

示例

public class ForkJoinDemo {
    public static class MyRecursiveTask extends RecursiveTask<Integer>{

        private int beginPosition;
        private int endPosition;

        public MyRecursiveTask(int beginPosition, int endPosition) {
            this.beginPosition = beginPosition;
            this.endPosition = endPosition;
        }

        @Override
        protected Integer compute() {
            System.out.println("beginPosition----"+beginPosition+"endPosition----"+endPosition);
            try {
                Thread.sleep(2000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            return beginPosition;
        }
    }


    public static void main(String[] args) {
        try {
            ForkJoinPool forkJoinPool = new ForkJoinPool(30);
            for (int i = 0;i < 50; i++){
                forkJoinPool.submit(new MyRecursiveTask(1,i+1));
            }
            Thread.sleep(800);
            System.out.println("并行数:"+forkJoinPool.getParallelism()+",池大小"+forkJoinPool.getPoolSize()+",已经提交未执行任务数"+forkJoinPool.getQueuedSubmissionCount()
            +",是否有未执行任务数"+forkJoinPool.hasQueuedSubmissions()+",当前活动的线程数"+forkJoinPool.getActiveThreadCount()+",任务的总数量"+forkJoinPool.getQueuedTaskCount()
            +",偷窃的任务个数"+forkJoinPool.getStealCount()+",正在运行不堵塞的线程数"+forkJoinPool.getRunningThreadCount()+",当前任务池是否静止未执行任务"+forkJoinPool.isQuiescent());
            forkJoinPool.shutdown();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

beginPosition----1endPosition----1
beginPosition----1endPosition----2
beginPosition----1endPosition----3
beginPosition----1endPosition----4
beginPosition----1endPosition----5
beginPosition----1endPosition----7
beginPosition----1endPosition----6
beginPosition----1endPosition----8
beginPosition----1endPosition----9
beginPosition----1endPosition----10
beginPosition----1endPosition----11
beginPosition----1endPosition----12
beginPosition----1endPosition----13
beginPosition----1endPosition----14
beginPosition----1endPosition----15
beginPosition----1endPosition----16
beginPosition----1endPosition----17
beginPosition----1endPosition----18
beginPosition----1endPosition----19
beginPosition----1endPosition----20
beginPosition----1endPosition----21
beginPosition----1endPosition----22
beginPosition----1endPosition----23
beginPosition----1endPosition----24
beginPosition----1endPosition----25
beginPosition----1endPosition----26
beginPosition----1endPosition----27
beginPosition----1endPosition----28
beginPosition----1endPosition----29
beginPosition----1endPosition----30
并行数:30,池大小30,已经提交未执行任务数20,是否有未执行任务数true,当前活动的线程数30,
任务的总数量0,偷窃的任务个数0,正在运行不堵塞的线程数0,当前任务池是否静止未执行任务false

类ForkJoinTask对异常的处理

方法 isCompletedAbnormally()判断任务是否出现异常,方法 isCompletedNormally()断任务是否正常执行完毕,方法 getException()返回报错异常。
示例

public class ForkJoinDemo {
    public static class MyRecursiveTask extends RecursiveTask<Integer>{

        private int beginPosition;
        private int endPosition;

        public MyRecursiveTask(int beginPosition, int endPosition) {
            this.beginPosition = beginPosition;
            this.endPosition = endPosition;
        }

        @Override
        protected Integer compute() {
            System.out.println("beginPosition----"+beginPosition+"endPosition----"+endPosition);
            try {
                Thread.sleep(1000);
                Integer.parseInt("A");
            } catch (InterruptedException e) {
                e.printStackTrace();
            }catch (NumberFormatException e){
                throw e;
            }
            return beginPosition;
        }
    }


    public static void main(String[] args) {
        try {
            ForkJoinPool forkJoinPool = new ForkJoinPool();
            ForkJoinTask task = forkJoinPool.submit(new MyRecursiveTask(1,10));
            //判断异常
            System.out.println("子任务是否有异常抛出" + task.isCompletedAbnormally()+",子任务是否正常结束" + task.isCompletedNormally());
            Thread.sleep(1500);
            System.out.println("子任务是否有异常抛出" + task.isCompletedAbnormally()+",子任务是否正常结束" + task.isCompletedNormally());
            //获取异常
            System.out.println(task.getException());
            forkJoinPool.shutdown();
        } catch (Exception e) {
            System.out.println("main捕获到异常!");
            e.printStackTrace();
        }
    }
}

子任务是否有异常抛出false,子任务是否正常结束false
beginPosition----1endPosition----10
子任务是否有异常抛出true,子任务是否正常结束false
java.lang.NumberFormatException

总结

介绍了 Fork-Join 分治编程类主要的 API 需要细化掌握 ForkJoinTask常用子类的 fork 分解算法,虽然分治编程可以有效地利用 CPU 资源,但不要为了分治编程而分治,应该结合具体的业务场景来进行使用。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值