并发编程(十三):Fork-Join框架


1.Fork/Join框架简介

Java7提供的一个并行任务执行,把大任务分割成小任务,最终汇总每个小任务结果后得到大任务结果的框架

Fork/Join运行流程图:


2.工作窃取算法

某个线程从其他队列窃取任务来执行

大任务分割为子任务,将子任务放在不同的任务队列中,每个队列创建一个单独线程来执行队列里的任务,线程和队列一一对应

工作窃取:有的线程把自己队列中的任务干完之后,会去其他线程的队列中窃取一个任务执行,通常使用双端队列,被窃取任务的线程从双端队列头部拿任务执行,窃取任务的线程从双端队列尾部拿任务执行

工作窃取算法优缺点:

  • 优点:充分利用线程进行并行计算,减少线程间的竞争
  • 缺点:消耗更多系统资源,只有一个任务时会出现竞争

3.Fork/Join框架设计

主要步骤:分割任务,执行任务,合并结果

  • 分割任务:需要一个fork类把大任务拆成子任务,子任务太大可能还要分割
  • 执行任务并合并结果:子任务在双端队列中执行,结果统一放在一个队列里,启动一个线程从队列里拿数据,然后合并这些数据

需要使用两个类:

  • ForkJoinTask:创建fork-join任务,提供了fork()和join()实现机制,通常情况下继承它的子类即可:RecursiveAction(无返回值),RecursiveTask(有返回值)
  • ForkJoinPool:执行fork-join任务

4.使用Fork/Join框架

创建任务:重写了RecursiveTask的compute()方法

// 如果任务大于阈值,就分裂成两个子任务计算 
int middle = (start + end) / 2; 
CountTask leftTask = new CountTask(start, middle); 
CountTask rightTask = new CountTask(middle + 1, end); 
// 执行子任务 
leftTask.fork(); 
rightTask.fork(); 
// 等待子任务执行完,并得到其结果 
int leftResult=leftTask.join(); 
int rightResult=rightTask.join(); 
// 合并子任务 
sum = leftResult + rightResult; 

使用Fork-Join池执行:

ForkJoinPool forkJoinPool = new ForkJoinPool(); 
// 生成一个计算任务,负责计算1+2+3+4 
CountTask task = new CountTask(1, 4); 
// 执行一个任务 ,返回一个Future
Future<Integer> result = forkJoinPool.submit(task); 
try { 
    System.out.println(result.get()); 
} catch (InterruptedException e) { 
} catch (ExecutionException e) { 
} 

5.Fork/Join框架异常处理

ForkJoinTask提供了isCompletedAbnormally()方法检查任务是否抛出异常或被取消,通过getException()方法获取异常:

if(task.isCompletedAbnormally()) { 
	System.out.println(task.getException()); 
} 

6.Fork/Join框架实现原理

ForkJoinPool由任务数组ForkJoinTask和工作线程数组ForkJoinWorkerThread组成

  • ForkJoinTask负责存放提交的任务
  • ForkJoinWorkerThread负责执行这些任务

6.1 fork()方法的实现

public final ForkJoinTask<V> fork() {
    //调用pushTask执行任务
   ((ForkJoinWorkerThread) Thread.currentThread())
        .pushTask(this);
   return this;
}

pushTask把当前任务存放在ForkJoinTask数组中,调用signalWork()唤醒或创建一个工作线程来执行任务:

final void pushTask(ForkJoinTask<> t) {
    ForkJoinTask<>[] q; 
    //s:队头,m:队尾
    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();
   }
} 

6.2 join()方法的实现

主要作用是阻塞当前线程并等待获取结果

public final V join() {
   //通过doJoin得到任务状态
   if (doJoin() != NORMAL)
        return reportResult();
   else
        return getRawResult();
}
private V reportResult() {
   int s; Throwable ex;
   if ((s = status) == CANCELLED)
        throw new CancellationException();
   if (s == EXCEPTIONAL && (ex = getThrowableException()) != null)
        UNSAFE.throwException(ex);
   return getRawResult();
} 

四种任务状态:

  • 已完成(NORMAL):直接返回任务结果
  • 被取消(CANCELLED):直接抛出CancllationException
  • 信号(SIGNAL)
  • 出现异常(EXCEPTIONAL):直接抛出对应异常

6.3 doJoin()实现

private int doJoin() {
    Thread t; ForkJoinWorkerThread w; int s; boolean completed;
    //线程是ForkJoinWorkerThread
    if ((t = Thread.currentThread()) instanceof ForkJoinWorkerThread) {
        //任务执行完,直接返回
        if ((s = status) < 0)
        	return s;
        //没有执行完,取出任务执行
        if ((w = (ForkJoinWorkerThread)t).unpushTask(this)) {
            try {
                //执行
            	completed = exec();
            } catch (Throwable rex) {
                //记录异常信息,并设置状态为EXCEPTIONAL
           		return setExceptionalCompletion(rex);
            }
            //顺利执行完
            if (completed)
            	return setCompletion(NORMAL);
        }
        return w.joinTask(this);
    }else
      //等待
      return externalAwaitDone();
}

posted @ 2021-03-11 21:16  菜鸟kenshine  阅读(1094)  评论(0编辑  收藏  举报