java高并发基础篇之ForkJoin框架

前言

从JDK1.7开始,Java提供Fork/Join框架用于并行执行任务,它的思想就是讲一个大任务分割成若干小任务,最终汇总每个小任务的结果得到这个大任务的结果。主要有两步:1、任务切分;2、结果合并;

其底层使用了工作窃取(work-stealing)算法:

工作窃取是指某个线程从其他队列里窃取任务来执行,一个大任务分割为若干个互不依赖的子任务,为了减少线程间的竞争,把这些子任务分别放到不同的队列里,并未每个队列创建一个单独的线程来执行队列里的任务,线程和队列一一对应。比如线程1负责处理1队列里的任务,2线程负责2队列的。但是有的线程会先把自己队列里的任务干完,而其他线程对应的队列里还有任务待处理。干完活的线程与其等着,不如帮其他线程干活,于是它就去其他线程的队列里窃取一个任务来执行。而在这时它们可能会访问同一个队列,所以为了减少窃取任务线程和被窃取任务线程之间的竞争,通常会使用双端队列,被窃取任务线程永远从双端队列的头部拿任务执行,而窃取任务线程永远从双端队列的尾部拿任务执行。

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

这一点和ThreadPoolExecutor不同,ThreadPoolExecutor是所有线程公用一个工作队列,所有线程都从这个工作队列中取任务

一、ForkJoinTask

其类定义:public abstract class ForkJoinTask extends Object implements Future, Serializable

1、ForkJoinTask的创建

static <T> ForkJoinTask<T>  adapt(Callable<? extends T> callable)

static ForkJoinTask<?>   adapt(Runnable runnable) //join()返回null

static <T> ForkJoinTask<T> adapt(Runnable runnable, T result) //join()返回result

2、内部任务提交和弹出

ForkJoinTask<V>  fork() //提交,返回task,可以进行控制:比如获取、取消等操作

boolean   tryUnfork() //尝试将任务弹出队列

V  invoke() //返回结果

static <T extends ForkJoinTask<?>> Collection<T> invokeAll(Collection<T> tasks)

static void invokeAll(ForkJoinTask<?>... tasks)

static void  invokeAll(ForkJoinTask<?> t1, ForkJoinTask<?> t2)

3、获取当前任务的结果

其他方法:abstract V getRawResult() 返回由join()返回的结果,即使此任务异常完成,或null如果此任务未知已完成。 此方法旨在帮助调试,以及支持扩展。 在任何其他情况下都不鼓励使用它

//等待计算完成,然后检索其结果,会阻塞,如果线程内部出现错误,我们可以通过ExecutionException异常获取
V    get()  throws InterruptedException, ExecutionException 
V    get(long timeout, TimeUnit unit) throws InterruptedException, ExecutionException,TimeoutException

//当 is done返回计算结果,会阻塞
//该方法与get()的不同之处在于,异常完成导致RuntimeException或Error ,而不是ExecutionException ,如果异常返回值是null
//并且调用线程的中断不会导致该方法通过投掷InterruptedException突然返回。
V    join()
public class RecursiveTasktest {
    private static int[] a = new int[1000];
    static {
        for(int i = 0;i<a.length;i++){
            a[i] = i;
        }
    }
    static class MyRecursiveTaskTest extends RecursiveTask<Integer> {
        final int start; //开始计算的数
        final int end; //最后计算的数
        MyRecursiveTaskTest(int start, int end) {
            this.start = start;
            this.end = end;
            Comparable c;
        }
        @Override
        protected Integer compute() {
            //如果计算量小于1000,那么分配一个线程执行if中的代码块,并返回执行结果
            if(end - start < 100) {
                System.out.println(Thread.currentThread().getName() + " 开始执行:" + start + "-" + end);
                int sum = 0;
                for(int i = start; i <=end; i++){
                    sum += a[i];
                   // int c = 0/0; 异常代码
                }
                return sum;
            }
            //如果计算量大于1000,那么拆分为两个任务
            MyRecursiveTaskTest task1 = new MyRecursiveTaskTest(start, (start + end) / 2);
            MyRecursiveTaskTest task2 = new MyRecursiveTaskTest((start + end) / 2 + 1, end);
            //执行任务
            task1.fork();
            task2.fork();
            //获取任务执行的结果
            return task1.join() + task2.join();
        }
        public static ForkJoinTask<?> pollTask(){
           return ForkJoinTask.pollTask();
        }
    }
}

测试1:正常返回

public static void main(String[] args) throws InterruptedException, ExecutionException {
        ForkJoinPool pool = new ForkJoinPool();
        MyRecursiveTaskTest task = new MyRecursiveTaskTest(0, a.length-1);
        pool.submit(task);
        System.out.println(task.join()); //499500
        System.out.println(task.get()); //499500
    }

测试2: 开启异常代码,异常返回

public static void main(String[] args) throws InterruptedException {
        ForkJoinPool pool = new ForkJoinPool();
        MyRecursiveTaskTest task = new MyRecursiveTaskTest(0, a.length-1);
        pool.submit(task);
        try {
            System.out.println(task.get());
        } catch (ExecutionException e) {
            System.out.println(e.getMessage()); //java.lang.ArithmeticException
        }
        System.out.println(task.join()); // Exception in thread "main" java.lang.ArithmeticException.....
    }

4、返回当前任务是否完成

boolean    isCancelled() //如果此任务在正常完成之前被取消,则返回 true 。
boolean    isCompletedAbnormally() //如果此任务抛出异常或被取消,返回 true 。
boolean    isCompletedNormally() //如果此任务完成而不抛出异常并且未被取消,则返回 true 。
boolean    isDone() //返回 true如果任务已完成。

5、结果设置

void complete(V value) //如果任务没有完成,就设置任务的结果值
void completeExceptionally(Throwable ex) //如果任务没有完成,设置任务抛出给定异常
static boolean    inForkJoinPool() //前线程是ForkJoinWorkerThread执行作为ForkJoinPool计算,返回true
protected abstract void    setRawResult(V value) //强制给定的值作为结果返回。

六、信息获取

static int getQueuedTaskCount()
返回当前线程已经fork但是没有执行的任务数量
static ForkJoinPool getPool()
当前任务执行的池,如果此任务在任何ForkJoinPool之外执行,则返回null

任务的标签

boolean compareAndSetForkJoinTaskTag(short e, short tag)

short getForkJoinTaskTag()
初始值0
short setForkJoinTaskTag(short tag)
原子地设置此任务的标签值
void reinitialize()
重置此任务的内部簿记状态,允许随后的 fork

protected abstract boolean exec()
立即执行此任务的基本操作,并返回true,如果从此方法返回后,此任务将保证已正常完成。

Throwable getException()
返回由基础计算抛出的异常,或 CancellationException取消,如果,或 null如果没有,或者如果方法尚未完成。

static int getSurplusQueuedTaskCount()
返回当前工作线程保留的本地排队任务数量多于可能窃取它们的其他工作线程的估计值,如果该线程未在ForkJoinPool中运行,则返回零
static void helpQuiesce()
可能执行任务,直到托管当前任务的池 is quiescent 。

protected static ForkJoinTask peekNextLocalTask()
返回,但不会取消调度或执行当前线程排队但尚未执行的任务(如果可以立即可用)。
protected static ForkJoinTask pollNextLocalTask()
如果当前线程正在ForkJoinPool中运行,则不执行当前线程排队的下一个任务但尚未执行的时间并返回。
protected static ForkJoinTask pollTask()
如果当前线程在ForkJoinPool中运行,则不执行下一个任务,返回当前线程排队的下一个任务,但尚未执行,如果一个可用,或者如果不可用,则由其他线程分派的任务,如果可供使用的话。

void quietlyComplete()
正常完成此任务而不设置值。
void quietlyInvoke()
执行此任务并等待其完成(如有必要),而不返回其结果或抛出异常。
void quietlyJoin()
加入此任务,而不返回其结果或抛出异常。

你可能感兴趣的:(JAVA基础)