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

JDK7引入的Fork/Join有三个核心类:

ForkJoinPool,执行任务的线程池

ForkJoinWorkerThread,执行任务的工作线程

ForkJoinTask,一个用于ForkJoinPool的任务抽象类。

因为ForkJoinTask比较复杂,抽象方法比较多,日常使用时一般不会继承ForkJoinTask来实现自定义的任务,而是继承ForkJoinTask的两个子类:

RecursiveTask:子任务带返回结果时使用

RecursiveAction:子任务不带返回结果时使用

对于Fork/Join框架的原理,Doug Lea的文章:A Java Fork/Join Framework

在看了网上的很多例子之后,发现在自定义任务类实现compute方法的逻辑一般是这样的:

if 任务足够小
直接返回结果
else
分割成N个子任务
依次调用每个子任务的fork方法执行子任务
依次调用每个子任务的join方法合并执行结果

而执行该自定义任务的调用的则是ForkJoinPool的execute方法,因此首先来看的就是ForkJoinPool的execute方法,看看和普通线程池执行任务有什么不同:

    public void execute(ForkJoinTask<?> task) {
if (task == null)
throw new NullPointerException();
forkOrSubmit(task);
}

因此forkOrSubmit是真正执行ForkJoinTask的方法:

    private <T> void forkOrSubmit(ForkJoinTask<T> task) {
ForkJoinWorkerThread w;
Thread t = Thread.currentThread();
if (shutdown)
throw new RejectedExecutionException();
if ((t instanceof ForkJoinWorkerThread) &&
(w = (ForkJoinWorkerThread)t).pool == this)
w.pushTask(task);
else
// 正常执行的时候是主线程调用的,因此关注addSubmission
addSubmission(task);
}

那么我们首先要关注的是addSubmission方法,发觉所做的事情和普通线程池很类似,就是把任务加入到队列中,不同的是直接使用Unsafe操作内存来添加任务对象

    private void addSubmission(ForkJoinTask<?> t) {
final ReentrantLock lock = this.submissionLock;
lock.lock();
try {
// 队列只是普通的数组而不是普通线程池的BlockingQueue,
// 唤醒worker线程的工作由下面的signalWork来完成
// 使用Unsafe进行内存操作,把任务放置在数组中
ForkJoinTask<?>[] q; int s, m;
if ((q = submissionQueue) != null) {
long u = (((s = queueTop) & (m = q.length-1)) << ASHIFT)+ABASE;
UNSAFE.putOrderedObject(q, u, t);
queueTop = s + 1;
if (s - queueBase == m)
// 数组已满,为数组扩容
growSubmissionQueue();
}
} finally {
lock.unlock();
}
// 通知有新任务来了:两种操作,有空闲线程则唤醒该线程
// 否则如果可以新建worker线程则为这个任务新建worker线程
// 如果不可以就返回了,等到有空闲线程来执行这个任务
signalWork();
}

接下来要弄清楚就是在compute中fork时,按道理来说这个动作是和主任务在同一个线程中执行,fork是如果把子任务变成多线程执行的:

    public final ForkJoinTask<V> fork() {
((ForkJoinWorkerThread) Thread.currentThread())
.pushTask(this);
return this;
}

在上面分析forkOrSubmit的时候同样见到了ForkJoinWorkerThread的pushTask方法调用,那么来看这个方法:

    final void pushTask(ForkJoinTask<?> t) {
// 代码的基本逻辑和ForkJoinPool的addSubmission方法基本一致
// 都是把任务加入了任务队列中,这里是加入到ForkJoinWorkerThread
// 内置的任务队列中
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();
}
}

看到这里一下子陷入了僵局,为什么ForkJoinWorkerThread要内建一个队列呢,而且如果子任务仍旧在同一个线程内的话,何以实现并发执行子任务呢?下一篇文章继续。

《java.util.concurrent 包源码阅读》22 Fork/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 包源码阅读》24 Fork/Join框架之Work-Stealing

    仔细看了Doug Lea的那篇文章:A Java Fork/Join Framework 中关于Work-Stealing的部分,下面列出该算法的要点(基本是原文的翻译): 1. 每个Worker线程 ...

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

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

随机推荐

  1. win10 uwp 自定义控件初始化

    我遇到一个问题,我在 xaml 用了我的自定义控件,但是我给他设置了一个值,但是什么时候我才可以获得这个值? 本文告诉大家,从构造函数.loaded.Initialized 的调用过程. 用最简单的方 ...

  2. IDEA启动后页面没有tomcat server选项,显示灰色问号和红叉不能使用

    说明:自己好几次硬盘莫名其妙读不出来导致电脑重启后idea没有了tomcat选项,原来的tomcat上显示灰色的问号和红色小叉子,网上搜了好久加上自己摸索,终于解决了.现在记一下也分享一下,省的下回又 ...

  3. 【转】C语言中动态分配数组

    原文地址:http://blog.chinaunix.net/uid-11085590-id-2914577.html 如何动态的定义及使用数组呢?记得一般用数组的时候都是先指定大小的.当时问老师,老 ...

  4. [BC]Four Inages Strategy(三维空间判断正方形)

    题目连接 :http://bestcoder.hdu.edu.cn/contests/contest_showproblem.php?cid=577&pid=1001 题目大意:在三维空间中, ...

  5. DNS over TLS到底有多牛?你想知道的都在这儿

    DNS over TLS,让电信.移动等各种ISP无法监视你的浏览轨迹...... SSL证书有助于客户端浏览器和网站服务器之间的加密连接. 这意味着在连接期间,所有的通信和活动都被遮蔽. 但通常意义 ...

  6. Hibernate 一对一双向映射 注解方式

    有外键的一方: @OneToOne(fetch = FetchType.LAZY) @JoinColumn(name = "courseid") public Tcourse ge ...

  7. LeetCode 563. Binary Tree Tilt (二叉树的倾斜度)

    Given a binary tree, return the tilt of the whole tree. The tilt of a tree node is defined as the ab ...

  8. LeetCode 53. Maximum Subarray(最大的子数组)

    Find the contiguous subarray within an array (containing at least one number) which has the largest ...

  9. css3+div画大风车

    今天已经礼拜三了,周天小颖家的佩佩就要结婚啦,小颖要去当伴娘了,哈哈哈哈哈哈,想想都觉得乐开了花,不过之前她给我说让我当她伴娘时,我说我要减肥,不然她那么瘦弱,我站旁边就感觉像一个圆滚滚的小皮球,小颖 ...

  10. 【Win 10 应用开发】UI Composition 札记(三):与 XAML 集成

    除了 DirectX 游戏开发,我们一般很少单独使用 UI Composition ,因此,与 XAML 互动并集成是必然结果.这样能够把两者的优势混合使用,让UI布局能够更灵活. 说到与 XAML ...