接下来看看调用ForkJoinTask的join方法都发生了什么:

    public final V join() {
// doJoin方法返回该任务的状态,状态值有三种:
// NORMAL, CANCELLED和EXCEPTIONAL
// join的等待过程在doJoin方法中进行
if (doJoin() != NORMAL)
// reportResult方法针对任务的三种状态有三种处理方式:
// NORMAL: 直接返回getRawResult()方法的返回值
// CANCELLED: 抛出CancellationException
// EXCEPTIONAL: 如果任务执行过程抛出了异常,则抛出该异常,否则返回getRawResult()
return reportResult();
else
// getRawResult是抽象方法,由子类来实现
return getRawResult();
}

RecursiveAction和RecursiveTask实现了getRawResult方法。

RecursiveAction用于没有返回值的场合,因此getRawResult方法返回null。

RecursiveTask用于有返回值的场合,因此返回的是抽象方法compute方法的返回值。

接下来继续看join的核心方法doJoin方法:

    private int doJoin() {
Thread t; ForkJoinWorkerThread w; int s; boolean completed;
// 针对ForkJoinWorkerThread调用join的情况
if ((t = Thread.currentThread()) instanceof ForkJoinWorkerThread) {
// status值的初始化值是0,在任务没有完成以前一直是非负值
// 因此一旦status的值变成负数,表示任务已经完成,直接返回
if ((s = status) < 0)
return s;
// 检查当前worker线程的任务栈(因为采用LIFO方式,所有这里称为栈)
// 的栈顶的任务是不是当前任务,如果是,从栈中取走该任务并执行
// 然后返回执行之后任务的状态
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();
}

前面文章中曾经举了几个例子来演示如何实现RecursiveTask的子类。在compute方法中会看到了join方法的调用,也就是ForkJoinWorkerThread调用join的情况。

因此首先来看ForkJoinWorkerThread的joinTask方法的实现:

    final int joinTask(ForkJoinTask<?> joinMe) {
ForkJoinTask<?> prevJoin = currentJoin;
currentJoin = joinMe;
for (int s, retries = MAX_HELP;;) {
// 当前任务已经完成,返回到前面一个join的任务
if ((s = joinMe.status) < 0) {
currentJoin = prevJoin;
return s;
} // 剩余的尝试次数大于0(MAX_HELP值为16)的情况,继续做尝试
if (retries > 0) {
if (queueTop != queueBase) {
// 检查当前线程的任务栈,如果任务栈不为空,当前任务处在栈顶位置则
// 执行该任务返回true,否则返回false,直接认为尝试失败
if (!localHelpJoinTask(joinMe))
retries = 0;
}
// 尝试了最大允许次数的一半
else if (retries == MAX_HELP >>> 1) {
--retries;
// 检查当前任务是否在某个worker线程的任务队列的队首位置
// 如果是的话,偷走这个任务并且执行掉该任务。tryDeqAndExec
// 返回任务的status值,因此大于等于0意味着任务还没有执行结束,
// 当前线程让出控制权以便其他线程执行任务
if (tryDeqAndExec(joinMe) >= 0)
Thread.yield();
}
else
// helpJoinTask方法检查当前任务是不是被某个Worker线程偷走了,
// 并且是这个线程最新偷走的任务(currentSteal),如果是的话,
// 当前线程帮助执行这个任务,这个过程成功则返回true
retries = helpJoinTask(joinMe) ? MAX_HELP : retries - 1;
}
else {
// 尝试了最大允许次数还没有成功,重置以便再次尝试
retries = MAX_HELP; // 一轮尝试失败,进入进程池等待任务
pool.tryAwaitJoin(joinMe);
}
}
}

来看一轮尝试失败之后,调用线程池的tryAwaitJoin方法会发生一些什么:

    final void tryAwaitJoin(ForkJoinTask<?> joinMe) {
int s;
// 检查任务是否结束之前先清除当前线程的中断状态
// 因为tryAwaitDone会调用wait可能产生中断异常
Thread.interrupted();
// 任务还在执行的情况,否则执行完成就直接返回
if (joinMe.status >= 0) {
// blockedCount加1,把当前线程标记为阻塞
// 成功则返回true,否则返回false
if (tryPreBlock()) {
// 调用wait方法等待任务完成
joinMe.tryAwaitDone(0L);
// blockedCount减1,把当前线程标记为活跃状态
postBlock();
}
// 线程处于关闭状态的情况,取消该任务
else if ((ctl & STOP_BIT) != 0L)
joinMe.cancelIgnoringExceptions();
}
}

最后又回归到了原点,来看task的tryAwaitDone方法:

    final void tryAwaitDone(long millis) {
int s;
try {
// status为0,设为1。成功了然后才会用wait等待
if (((s = status) > 0 ||
(s == 0 &&
UNSAFE.compareAndSwapInt(this, statusOffset, 0, SIGNAL))) &&
status > 0) {
synchronized (this) {
if (status > 0)
wait(millis);
}
}
} catch (InterruptedException ie) {
// 因为wait被中断了,不能保证任务被正确执行结束,因此调用该方法时要注意
// 检查任务是否已经执行结束了
}

走完了Worker线程内的join的流程,最后来看其他线程join等待发生了什么,来看externalAwaitDone方法:

    private int externalAwaitDone() {
int s; if ((s = status) >= 0) {
boolean interrupted = false;
synchronized (this) {
// 循环等待直到任务执行结束
while ((s = status) >= 0) {
if (s == 0)
UNSAFE.compareAndSwapInt(this, statusOffset,
0, SIGNAL);
else {
try {
wait();
} catch (InterruptedException ie) {
interrupted = true;
}
}
}
}
// 清除中断状态
if (interrupted)
Thread.currentThread().interrupt();
}
return s;
}

externalAwaitDone逻辑较为简单,采用循环的方式,使用wait方法等待直到任务执行结束。

既然使用wait方法等待,那么必然在任务执行结束后需要调用notify或者notifyAll的方法,在setCompletion方法找到了:

    private int setCompletion(int completion) {
for (int s;;) {
if ((s = status) < 0)
return s;
if (UNSAFE.compareAndSwapInt(this, statusOffset, s, completion)) {
if (s != 0)
synchronized (this) { notifyAll(); }
return completion;
}
}
}

到这里把Fork/Join框架简单地讲完了,因为水平所限,遗漏了很多的细节,各位见谅。

《java.util.concurrent 包源码阅读》26 Fork/Join框架之Join的更多相关文章

  1. 《java.util.concurrent 包源码阅读》 结束语

    <java.util.concurrent 包源码阅读>系列文章已经全部写完了.开始的几篇文章是根据自己的读书笔记整理出来的(当时只阅读了部分的源代码),后面的大部分都是一边读源代码,一边 ...

  2. 《java.util.concurrent 包源码阅读》13 线程池系列之ThreadPoolExecutor 第三部分

    这一部分来说说线程池如何进行状态控制,即线程池的开启和关闭. 先来说说线程池的开启,这部分来看ThreadPoolExecutor构造方法: public ThreadPoolExecutor(int ...

  3. 《java.util.concurrent 包源码阅读》02 关于java.util.concurrent.atomic包

    Aomic数据类型有四种类型:AomicBoolean, AomicInteger, AomicLong, 和AomicReferrence(针对Object的)以及它们的数组类型, 还有一个特殊的A ...

  4. 《java.util.concurrent 包源码阅读》04 ConcurrentMap

    Java集合框架中的Map类型的数据结构是非线程安全,在多线程环境中使用时需要手动进行线程同步.因此在java.util.concurrent包中提供了一个线程安全版本的Map类型数据结构:Concu ...

  5. 《java.util.concurrent 包源码阅读》17 信号量 Semaphore

    学过操作系统的朋友都知道信号量,在java.util.concurrent包中也有一个关于信号量的实现:Semaphore. 从代码实现的角度来说,信号量与锁很类似,可以看成是一个有限的共享锁,即只能 ...

  6. 《java.util.concurrent 包源码阅读》06 ArrayBlockingQueue

    对于BlockingQueue的具体实现,主要关注的有两点:线程安全的实现和阻塞操作的实现.所以分析ArrayBlockingQueue也是基于这两点. 对于线程安全来说,所有的添加元素的方法和拿走元 ...

  7. 《java.util.concurrent 包源码阅读》09 线程池系列之介绍篇

    concurrent包中Executor接口的主要类的关系图如下: Executor接口非常单一,就是执行一个Runnable的命令. public interface Executor { void ...

  8. 《java.util.concurrent 包源码阅读》22 Fork/Join框架的初体验

    JDK7引入了Fork/Join框架,所谓Fork/Join框架,个人解释:Fork分解任务成独立的子任务,用多线程去执行这些子任务,Join合并子任务的结果.这样就能使用多线程的方式来执行一个任务. ...

  9. 《java.util.concurrent 包源码阅读》05 BlockingQueue

    想必大家都很熟悉生产者-消费者队列,生产者负责添加元素到队列,如果队列已满则会进入阻塞状态直到有消费者拿走元素.相反,消费者负责从队列中拿走元素,如果队列为空则会进入阻塞状态直到有生产者添加元素到队列 ...

随机推荐

  1. 设计模式:基于线程池的并发Visitor模式

    1.前言 第二篇设计模式的文章我们谈谈Visitor模式. 当然,不是简单的列个的demo,我们以电商网站中的购物车功能为背景,使用线程池实现并发的Visitor模式,并聊聊其中的几个关键点. 一,基 ...

  2. 如何利用Oracle VM Templates 在几分钟内部署Oracle Real Application Clusters (RAC)

    本文未经授权,禁止一切形式的转载.如果对本文有任何疑问可以通过以下方式和我交流: 邮箱: jiangxinnju@163.com 博客园地址: http://www.cnblogs.com/jiang ...

  3. 主要讲下hack的兼容用法,比较浅,哈哈

    hack是主要来处理IE的兼容,不同的IE,不同的兼容方式 /*   属性前缀法(即类内部Hack):       *color:#000; *号对IE6,IE7都生效   +color:#555; ...

  4. 双向链表--Java实现

    /*双向链表特点: *1.每个节点含有两个引用,previos和next,支持向前或向后的遍历(除头节点) *2.缺点插入或删除的时候涉及到引用修改的比较多 *注意:下面的双向链表其实也实现了双端链表 ...

  5. linux安装redis-3.0.7

    一.Redis介绍 1.简介 Redis是当前比较热门的NOSQL系统之一,它是一个key-value存储系统.和Memcache类似,但很大程度补偿了Memcache的不足,它支持存储的value类 ...

  6. yii2之数据验证

    一.场景 什么情况下需要使用场景呢?当一个模型需要在不同情境中使用时,若不同情境下需要的数据表字段和数据验证规则有所 不同,则需要定义多个场景来区分不同使用情境.例如,用户注册的时候需要填写email ...

  7. 开源纯C#工控网关+组态软件(四)上下位机通讯原理

    一.   网关的功能:承上启下 最近有点忙,更新慢了.感谢园友们给予的支持,现在github上已经有.目标是最好的开源组态,看来又近一步^^ 之前有提到网关是物联网的关键环节,它的作用就是承上启下. ...

  8. JAVA提高十一:LinkedList深入分析

    上一节,我们学习了ArrayList 类,本节我们来学习一下LinkedList,LinkedList相对ArrayList而言其使用频率并不是很高,因为其访问元素的性能相对于ArrayList而言比 ...

  9. Problem B

    Problem Description A subsequence of a given sequence is the given sequence with some elements (poss ...

  10. Luogu 2296 寻找道路

    https://www.luogu.org/problemnew/show/2296 题目描述 在有向图G 中,每条边的长度均为1 ,现给定起点和终点,请你在图中找一条从起点到终点的路径,该路径满足以 ...