简介

ForkJoinPool管理着ForkJoinWorkerThread线程,ForkJoinWorkerThread线程内部有一个双端队列,这个双端队列主要由一个数组queue、数组下标queueBase、数组上标queueTop三个值保证。

ForkJoinTask<?>[] queue:数组的大小必须是2的n次方,方便将取模转换为移位运算;

int queueTop:标识下一个被push或者pop的位置,这个值只会被当前线程修改,因些没有加volatile修饰;

volatile int queueBase:下一个可以被其他线程steal的位置,由于其他线程会修改这个值,所以用volatile修饰保证可见性。

初始化

在线程的run方法启动时,会调用线程的onStart()方法,在这个方法中对queue进行了初始化,长度为1 << 13,这个方法并没有对queueTop,queueBase进行赋值,采用默认值0。

扩容

当向线程中添加任务时,有可能会导致数组满的情况,如下代码所示:

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();
}
}

其中s代表queueTop的值,m为数组长度-1,当s == m时,也就是queue数组中都放满任务了,这时需要对数组进行扩容。

private void growQueue() {
ForkJoinTask<?>[] oldQ = queue;
int size = oldQ != null ? oldQ.length << 1 : INITIAL_QUEUE_CAPACITY;
if (size > MAXIMUM_QUEUE_CAPACITY)
throw new RejectedExecutionException("Queue capacity exceeded");
if (size < INITIAL_QUEUE_CAPACITY)
size = INITIAL_QUEUE_CAPACITY;
ForkJoinTask<?>[] q = queue = new ForkJoinTask<?>[size];
int mask = size - 1;
int top = queueTop;
int oldMask;
if (oldQ != null && (oldMask = oldQ.length - 1) >= 0) {
for (int b = queueBase; b != top; ++b) {
long u = ((b & oldMask) << ASHIFT) + ABASE;
Object x = UNSAFE.getObjectVolatile(oldQ, u);
if (x != null && UNSAFE.compareAndSwapObject(oldQ, u, x, null))
UNSAFE.putObjectVolatile
(q, ((b & mask) << ASHIFT) + ABASE, x);
}
}
}

从以上扩容代码可以看出,最大容量不能超过MAXIMUM_QUEUE_CAPACITY(1 << 24),最小不能小于初始值。每次扩容为先前大小的2倍,将原始数组复制到新数组中,同时将旧数组置null。扩容的过程中,queueBase和queueTop并不需要变化。

入队列

向线程队列中添加一个任务,或者向线程池添加一个任务时,如果这个任务是一个ForkJoinTask实例,就会做入队列的操作。前面已有这段代码,这里简要分析一下

long u = (((s = queueTop) & (m = q.length - 1)) << ASHIFT) + ABASE;
UNSAFE.putOrderedObject(q, u, t);
queueTop = s + 1;

第一行,找到queueTop在数组中的位置

第二行,用新任务填充queueTop所在位置

第三行,queueTop加1.

出队列

本地线程需要执行一个任务

final void execTask(ForkJoinTask<?> t) {
currentSteal = t;
for (;;) {
if (t != null)
t.doExec();
if (queueTop == queueBase)
break;
t = locallyFifo ? locallyDeqTask() : popTask();
}
++stealCount;
currentSteal = null;
}

注意locallyFifo 这个属性,是否对自己的队列采用FIFO策略,默认为false,即默认从queueTop一端取任务。如果这个值为false,则从queueBase一端取数据。这个值可以通过ForkJoinPool类的asyncMode属性加以修改。

final ForkJoinTask<?> locallyDeqTask() {
ForkJoinTask<?> t; int m, b, i;
ForkJoinTask<?>[] q = queue;
if (q != null && (m = q.length - 1) >= 0) {
while (queueTop != (b = queueBase)) {
if ((t = q[i = m & b]) != null &&
queueBase == b &&
UNSAFE.compareAndSwapObject(q, (i << ASHIFT) + ABASE,
t, null)) {
queueBase = b + 1;
return t;
}
}
}
return null;
} private ForkJoinTask<?> popTask() {
int m;
ForkJoinTask<?>[] q = queue;
if (q != null && (m = q.length - 1) >= 0) {
for (int s; (s = queueTop) != queueBase;) {
int i = m & --s;
long u = (i << ASHIFT) + ABASE; // raw offset
ForkJoinTask<?> t = q[i];
if (t == null) // lost to stealer
break;
if (UNSAFE.compareAndSwapObject(q, u, t, null)) {
queueTop = s; // or putOrderedInt
return t;
}
}
}
return null;
}

关键两句话:

queueBase = b + 1:FIFO策略每次从queueBase取任务,每取一个,queueBase增加1;
--s,queueTop = s:LIFO策略每次从queueTop取任务,每取一个,queueTop减1。

其他线程需要偷一个任务执行

以下是work-stealing的核心代码

for (;;) {
ForkJoinTask<?>[] q; int b, i;
if (joinMe.status < 0)
break outer;
if ((b = v.queueBase) == v.queueTop ||
(q = v.queue) == null ||
(i = (q.length-1) & b) < 0)
break; // empty
long u = (i << ASHIFT) + ABASE;
ForkJoinTask<?> t = q[i];
if (task.status < 0)
break outer; // stale
if (t != null && v.queueBase == b &&
UNSAFE.compareAndSwapObject(q, u, t, null)) {
v.queueBase = b + 1;
v.stealHint = poolIndex;
ForkJoinTask<?> ps = currentSteal;
currentSteal = t;
t.doExec();
currentSteal = ps;
helped = true;
}
}

1、瞄到第i个位置这个任务,i = (q.length-1) & b,i其实就是queueBase在数组中所在的位置;
2、将这个位置上的任务设置为null,并增加queueBase的值,设置stealHint表示你的东西被我偷了;

3、保存先前的currentSteal值,设置currentSteal为这个偷来的task,然后执行这个task,执行完后,恢复currentSteal的值。

Fork/Join框架之双端队列的更多相关文章

  1. Fork/Join 框架-设计与实现(翻译自论文《A Java Fork/Join Framework》原作者 Doug Lea)

    作者简介 Dong Lea任职于纽约州立大学奥斯威戈分校(State University of New York at Oswego),他发布了第一个广泛使用的java collections框架实 ...

  2. java多线程8:阻塞队列与Fork/Join框架

    队列(Queue),是一种数据结构.除了优先级队列和LIFO队列外,队列都是以FIFO(先进先出)的方式对各个元素进行排序的. BlockingQueue 而阻塞队列BlockingQueue除了继承 ...

  3. 聊聊并发(八)——Fork/Join框架介绍

      作者 方腾飞 发布于 2013年12月23日 | 被首富的“一个亿”刷屏?不如定个小目标,先把握住QCon上海的优惠吧!2 讨论 分享到:微博微信FacebookTwitter有道云笔记邮件分享 ...

  4. Java并发——Fork/Join框架

    为了防止无良网站的爬虫抓取文章,特此标识,转载请注明文章出处.LaplaceDemon/ShiJiaqi. http://www.cnblogs.com/shijiaqi1066/p/4631466. ...

  5. 转:聊聊并发(八)——Fork/Join框架介绍

    1. 什么是Fork/Join框架 Fork/Join框架是Java7提供了的一个用于并行执行任务的框架, 是一个把大任务分割成若干个小任务,最终汇总每个小任务结果后得到大任务结果的框架. 我们再通过 ...

  6. Java并发编程--Fork/Join框架使用

    上篇博客我们介绍了通过CyclicBarrier使线程同步,可是上述方法存在一个问题,那就是假设一个大任务跑了2个线程去完毕.假设线程2耗时比线程1多2倍.线程1完毕后必须等待线程2完毕.等待的过程线 ...

  7. 多线程(五) Fork/Join框架介绍及实例讲解

    什么是Fork/Join框架 Fork/Join框架是Java7提供了的一个用于并行执行任务的框架, 是一个把大任务分割成若干个小任务,最终汇总每个小任务结果后得到大任务结果的框架. 我们再通过For ...

  8. Java Fork/Join 框架

    简介 从JDK1.7开始,Java提供Fork/Join框架用于并行执行任务,它的思想就是讲一个大任务分割成若干小任务,最终汇总每个小任务的结果得到这个大任务的结果. 这种思想和MapReduce很像 ...

  9. Fork/Join框架详解

    Fork/Join框架是Java 7提供的一个用于并行执行任务的框架,是一个把大任务分割成若干个小任务,最终汇总每个小任务结果后得到大任务结果的框架.Fork/Join框架要完成两件事情: 1.任务分 ...

随机推荐

  1. Vs2015 win10虚拟机启动问题:无法设置UDP端口 解决方法 合集(转载)

    刚装的vs2015 社区版 出现这个问题,wp8.1和win10m模拟器都无法启动,找了好久找到的解决方案,放这儿供大家参考,免得大家像我一样走弯路: Windows Phone emulator n ...

  2. ansible安装及问题解决

    本文主要用来安装ansible,在进行安装的时候,也可以使用其他的版本进行安装,本文主要讲述安装ansible的步骤,还有常用问题的解决. 1.查看python版本 由此可以看到python的版本为2 ...

  3. windows分屏

    一.准备 主机.显示屏A.显示屏B.DVI连接线2根 二.操作步骤 1.使用DVI连接线将显示屏A连接到主机上,开机进入windows系统(演示用的是win 7)(若已连接,请跳到第2步.基本上这一步 ...

  4. 在RHEL5.4下安装ORACLE11G

    以root身份登录到系统,新增组和用户: #groupadd oinstall #groupadd dba #useradd -g oinstall -G dba oracle #passwd ora ...

  5. Quora 用了哪些技术(转)

    原文:http://dbanotes.net/arch/quora_tech.html 很多团队都在学习.研究 Quora .前段时间看到这篇 Quora’s Technology Examined  ...

  6. 我的第一个CUDA程序

    最近在学习CUDA框架,折腾了一个多月终于把CUDA安装完毕,现在终于跑通了自己的一个CUDA的Hello world程序,值得欣喜~ 首先,关于CUDA的初始化,代码和解释如下,这部分主要参考GXW ...

  7. C#操作Access的一些小结

    C#操作Access的一些小结 好久没有写blog,感觉今年一年都没怎么真正开心过,整天有一些事围绕在身边,使心情难以平静下来,真正写点有意义的东西.博客园是天天看的,看得多,写的少,偶尔也是Copy ...

  8. HDU 4602 Magic Ball Game(离线处理,树状数组,dfs)

    Magic Ball Game Time Limit: 10000/5000 MS (Java/Others)    Memory Limit: 65536/65536 K (Java/Others) ...

  9. 1001.A+B Format (20)(思路,bug发现及其修改,提交记录)

    https://github.com/031502316a/object-oriented/tree/master/1001 ---恢复内容开始--- 1.解题思路 一开始见到题目时,感觉难的就是输出 ...

  10. RS232串口用事件接受数据(一问一答)

    private void button1_Click(object sender, EventArgs e) { serialPort1.Open(); serialPort1.DataReceive ...