并发编程之Fork/Join框架

java7开始,concurrent包下新增Fork/Join框架,用于并行执行任务,它把大任务切分成多个小任务,最后汇集每个小任务的计算结果,从这点功能上来说,和java里的异步任务FutureTask有些许的类似。例如导出功能,要一次导出50个大文件,此时就可以使用Fork/Join框架来切分任务,分成10个小任务,每批导5个文件,一方面变常规的串行化方式为并行方式,另一方面也极大的加快任务的执行速度。使用fork/join框架会一直切分大任务为小任务,直到不能切分为止。

Fork/Join框架使用

  1. 创建ForkJoin任务(主要包含fork()、join()方法),继承ForkJoinTask的子类
  2. 通过ForkJoinPool(池)执行ForkJoinTask
其中ForkJoinTask的子类RecursiveAction用于没有返回结果的任务,RecursiveTask则用于有返回值的任务。如下模拟求和功能
public class SumTask extends RecursiveTask<Integer> {

    private static final int THRESHOLD =3; //设定一个阈值

    Integer start;
    Integer end;

    public SumTask(Integer start, Integer end) {
        this.start = start;
        this.end = end;
    }

    @Override
    protected Integer compute() {
        // 如果任务小于阈值,说明任务足够小,不切分直接计算
        int sum = 0;
        if (end - start <= THRESHOLD) {
            for (int i = start; i <= end; i++) {
                sum += i;
            }
            return sum;
        }
        // 超过阈值
        int middle = (end + start) / 2;
        SumTask subtask1 = new SumTask(start, middle);
        SumTask subtask2 = new SumTask(middle+1, end);
        invokeAll(subtask1, subtask2);
        Integer result1 = subtask1.join();
        Integer result2 = subtask2.join();
        //合并任务
        sum = result1 + result2;
        System.out.println("result1+result2"+"="+sum);
        return sum;
    }

    public static void main(String[] args) throws ExecutionException, InterruptedException {
        ForkJoinPool joinPool = new ForkJoinPool();
        List<Integer> list = new ArrayList<Integer>();
        for (int i = 1; i <=10; i++) {
            list.add(i);  //size=10
        }
        SumTask task = new SumTask(1,10);
        ForkJoinTask<Integer> result = joinPool.submit(task);
        Integer integer = result.get();
        System.out.println(integer);
    }

}

输出结果如下,可以看到,在计算10以内的求和运算时,fork/join拆分为了 1-5、6-10两个任务,进而合并和这两个任务的计算结果
result1+result2=15
result1+result2=40
result1+result2=55
55

主要的逻辑都在compute方法中,通过阈值的判断任务是否足够小而进一步决定是否切分,足够小就直接执行,否则就切分为两个子任务,每个子任务在进行fork方法时,又进入compute方法看子任务是否符合继续切分条件,如果不需要切分,则执行子任务并返回result,join方法可以拿到子任务的计算结果。

fork/join框架的异常捕获

由于任务在执行过程中无法直接捕获到主线程中抛出的异常,fork/join框架提供了isCompletedAbnormally()方法来判断任务是否被取消或抛出异常,并通过getException()方法获取异常
if(task.isCompletedAbnormally()){
    System.out.println(task.getException());
}

当任务无异常或没有完成,getException()方法返回null;若任务被取消,则返回CancellationException

fork/join实现原理

RecursiveAction类或者RecursiveTask继承自ForkJoinTask类,其关键方法为fork()和join方法;而执行fork/join框架的执行者-ForkJoinPool,其中的主要对象为ForkJoinWorkerThread数组和ForkJoinTask数组,ForkJoinTask负责将任务提交给ForkJoinPool,ForkJoinWorkerThread则负责执行这些任务。

fork方法

pushTask方法往ForkJoinTask数组里放任务--->signalWork方法唤醒或创建线程去执行任务-->
public final ForkJoinTask<V> fork() {
    ((ForkJoinWorkerThread) Thread.currentThread())
        .pushTask(this);
    return this;
}

final void pushTask(ForkJoinTask<?> t) {
    ForkJoinTask<?>[] q; int s, m;
    if ((q = queue) != null) {    // ignore if queue removed
        long u = (((s = queueTop) & (m = q.length - 1)) << ASHIFT) + ABASE;
        UNSAFE.putOrderedObject(q, u, t);
        queueTop = s + 1;         // or use putOrderedInt
        if ((s -= queueBase) <= 2)
            pool.signalWork();
        else if (s == m)
            growQueue();
    }
}

final void signalWork() {
    /*
     * The while condition is true if: (there is are too few total
     * workers OR there is at least one waiter) AND (there are too
     * few active workers OR the pool is terminating).  The value
     * of e distinguishes the remaining cases: zero (no waiters)
     * for create, negative if terminating (in which case do
     * nothing), else release a waiter. The secondary checks for
     * release (non-null array etc) can fail if the pool begins
     * terminating after the test, and don't impose any added cost
     * because JVMs must perform null and bounds checks anyway.
     */
    long c; int e, u;
    while ((((e = (int)(c = ctl)) | (u = (int)(c >>> 32))) &
            (INT_SIGN|SHORT_SIGN)) == (INT_SIGN|SHORT_SIGN) && e >= 0) {
        if (e > 0) {          // release a waiting worker
            int i; ForkJoinWorkerThread w; ForkJoinWorkerThread[] ws;
            if ((ws = workers) == null ||
                (i = ~e & SMASK) >= ws.length ||
                (w = ws[i]) == null)
                break;
            long nc = (((long)(w.nextWait & E_MASK)) |
                       ((long)(u + UAC_UNIT) << 32));
            if (w.eventCount == e && UNSAFE.compareAndSwapLong(this, ctlOffset, c, nc)) {
                w.eventCount = (e + EC_UNIT) & E_MASK;
                if (w.parked)
                    UNSAFE.unpark(w);
                break;
            }
        }
        else if (UNSAFE.compareAndSwapLong
                 (this, ctlOffset, c,
                  (long)(((u + UTC_UNIT) & UTC_MASK) |
                         ((u + UAC_UNIT) & UAC_MASK)) << 32)) {
            addWorker();
            break;
        }
    }
}

join方法

doJoin方法判断当前任务执行状态-->已取消:返回CancellationException异常;已完成:直接返回计算结果;任务发生异常:抛出相对应的异常信息。所以总的来说,join方法的作用就是阻塞线程来等待任务的完成。
public final V join() {
    if (doJoin() != NORMAL)
        return reportResult();
    else
        return getRawResult();
}

private int doJoin() {
    Thread t; ForkJoinWorkerThread w; int s; boolean completed;
    if ((t = Thread.currentThread()) instanceof ForkJoinWorkerThread) {
        if ((s = status) < 0)     
            return s;
        if ((w = (ForkJoinWorkerThread)t).unpushTask(this)) {
            try {
                completed = exec();
            } catch (Throwable rex) {
                return setExceptionalCompletion(rex);
            }
            if (completed)
                return setCompletion(NORMAL);
        }
        return w.joinTask(this);
    }
    else
        return externalAwaitDone();
}

猜你喜欢

转载自blog.csdn.net/fanrenxiang/article/details/80399087