ForkJoinPool实现原理和源码解析

这是在部门做技术分享的时候写的一篇学习笔记,顺便贴在这里给大家看看,欢迎指出错误,共同学习

ForkJoin是什么

ForkJoin是用于并行执行任务的框架, 是一个把大任务分割成若干个小任务,最终汇总每个小任务结果后得到大任务结果的框架。Fork就是把一个大任务切分为若干子任务并行的执行,Join就是合并这些子任务的执行结果,最后得到这个大任务的结果。
在这里插入图片描述

数据结构

在这里插入图片描述

关键调用图

在这里插入图片描述

源码解析

1、pool属性

    // Instance fields
    volatile long ctl;                   // 控制中心:非常重要,看下图解析
    volatile int runState;               // 负数是shutdown,其余都是2的次方
    final int config;                    // 配置:二进制的低16位代表 并行度(parallelism),
																					//高16位:mode可选FIFO_QUEUE(1 << 16)和LIFO_QUEUE(1 << 31),默认是LIFO_QUEUE
    int indexSeed;                       // 生成worker的queue索引
    volatile WorkQueue[] workQueues;     // main registry
    final ForkJoinWorkerThreadFactory factory;
    final UncaughtExceptionHandler ueh;  // per-worker UEH
    final String workerNamePrefix;       // to create worker name string
    volatile AtomicLong stealCounter;    // also used as sync monitor

2、控制中心:ctl

在这里插入图片描述

3、WorkQueue

        // Instance fields
        volatile int scanState;    // 负数:inactive, 非负数:active, 其中奇数代表scanning
        int stackPred;             // sp = (int)ctl, 前一个队列栈的标示信息,包含版本号、是否激活、以及队列索引
        int nsteals;               // 窃取的任务数
        int hint;                  // 一个随机数,用来帮助任务窃取,在 helpXXXX()的方法中会用到
        int config;                // 配置:二进制的低16位代表 在 queue[] 中的索引,
																	 // 高16位:mode可选FIFO_QUEUE(1 << 16)和LIFO_QUEUE(1 << 31),默认是LIFO_QUEUE
        volatile int qlock;        // 锁定标示位:1: locked, < 0: terminate; else 0
        volatile int base;         // index of next slot for poll
        int top;                   // index of next slot for push
        ForkJoinTask<?>[] array;   // 任务列表

4、externalPush || externalSubmit

    final void externalPush(ForkJoinTask<?> task) {
        WorkQueue[] ws; WorkQueue q; int m;
        //我们以前常用的Random,在并发下,多个线程同时计算种子需要用到同一个原子变量。
        //由于更新操作使用CAS,同时执行只有一个线程成功,其他线程的大量自旋造成性能损失,ThreadLocalRandom继承Random,对此进行了改进。
				//ThreadLocalRandom运用了ThreadLocal,每个线程内部维护一个种子变量,多线程下计算新种子时使用线程自己的种子变量进行更新,避免了竞争。
        int r = ThreadLocalRandom.getProbe();
        int rs = runState;
        // 外部提交的task,肯定会到偶数位下标的队列上
        // SQMASK = 0x007e = 1111110,任何数和 SQMASK 进行 & 运算 都会是偶数
        if ((ws = workQueues) != null && (m = (ws.length - 1)) >= 0 &&
            (q = ws[m & r & SQMASK]) != null && r != 0 && rs > 0 &&
            //队列上锁
            U.compareAndSwapInt(q, QLOCK, 0, 1)) {
            ForkJoinTask<?>[] a; int am, n, s;
            if ((a = q.array) != null &&
                (am = a.length - 1) > (n = (s = q.top) - q.base)) {
                int j = ((am & s) << ASHIFT) + ABASE;
                //把 task 放到队列的 top端
                U.putOrderedObject(a, j, task);
                U.putOrderedInt(q, QTOP, s + 1);
                U.putIntVolatile(q, QLOCK, 0);
                if (n <= 1)
                    signalWork(ws, q);
                return;
            }
            //队列解锁
            U.compareAndSwapInt(q, QLOCK, 1, 0);
        }
        externalSubmit(task);
    }

5、righsterWorker

    final WorkQueue registerWorker(ForkJoinWorkerThread wt) {
        //......
            if ((ws = workQueues) != null && (n = ws.length) > 0) {
                int s = indexSeed += SEED_INCREMENT;  // unlikely to collide
                int m = n - 1;
                // worker的queue肯定放在pool中的queue[]中的奇数下标
              	// m = ws.lenght - 1, ws.lenght 肯定是偶数,则m 肯定是奇数
                // 1的二进制位:00000001, 所以任何数 "|" 1 都是奇数
                // 所以 奇数 & 奇数 , 1&1 = 1,所以i肯定是奇数
                i = ((s << 1) | 1) & m;               // odd-numbered indices
                if (ws[i] != null) {                  // collision
                    int probes = 0;                   // step by approx half n
                    int step = (n <= 4) ? 2 : ((n >>> 1) & EVENMASK) + 2;
                    // 如果下标已经有队列,则重新生成奇数下标
                    // step肯定为偶数:EVENMASK:0xfffe:1111111111111110
                  	// 所以 奇数+偶数,奇偶性不变
                    while (ws[i = (i + step) & m] != null) {
                        if (++probes >= n) {
                            workQueues = ws = Arrays.copyOf(ws, n <<= 1);
                            m = n - 1;
                            probes = 0;
                        }
                    }
                }
                //...
            }
        //......
    }

6、scan:

private ForkJoinTask<?> scan(WorkQueue w, int r) {
        WorkQueue[] ws; int m;
        if ((ws = workQueues) != null && (m = ws.length - 1) > 0 && w != null) {
            int ss = w.scanState;                     // initially non-negative
           	// k = r & m 。 r是一个随机数,m 是 队列数组长度 - 1;用于定位去哪个 队列 窃取 task
            for (int origin = r & m, k = origin, oldSum = 0, checkSum = 0;;) {
                WorkQueue q; ForkJoinTask<?>[] a; ForkJoinTask<?> t;
                int b, n; long c;
                if ((q = ws[k]) != null) {
                  	// 如果有还没执行的task,尝试窃取队列q 中的base下标的 task。 即FIFO
                    // i: 在内存中,b下标对应的对象的偏移值。 a.length - 1 的二进制位 永远是 0[1...]s,所以 (a.length - 1) & b = b,主要是保证了b不会越界
                    if ((n = (b = q.base) - q.top) < 0 &&
                        (a = q.array) != null) {      // non-empty
                        long i = (((a.length - 1) & b) << ASHIFT) + ABASE;
                        if ((t = ((ForkJoinTask<?>)
                                  U.getObjectVolatile(a, i))) != null &&
                            q.base == b) {
                           	// ss 是小偷的 scanState,大于0代表当前的worker是激活的
                            if (ss >= 0) {
                              	// 把 task 从 队列中取出来,然后队列的base+1,如果被窃取的队列中有多于1个的task,则尝试唤醒其他的worker
                                if (U.compareAndSwapObject(a, i, t, null)) {
                                    q.base = b + 1;
                                    if (n < -1)       // signal others
                                        signalWork(ws, q);
                                    return t;
                                }
                            }
                          	// ss小于0代表当前的worker是未激活的,并且当前是第一次扫描,这时候尝试激活worker
                            // oldSum: 上一次遍历周期的 base 值的和。
                            // (int) c : 可以拿到当前栈顶的空闲worker。sp = (int) c
                            else if (oldSum == 0 &&   // try to activate
                                     w.scanState < 0)
                                tryRelease(c = ctl, ws[m & (int)c], AC_UNIT);
                        }
                        if (ss < 0)                   // refresh
                            ss = w.scanState;
                        // 更新随机值,重新初始化所有控制变量,重新定位队列
                        r ^= r << 1; r ^= r >>> 3; r ^= r << 10;
                        origin = k = r & m;           // move and rescan
                        oldSum = checkSum = 0;
                        continue;
                    }
                    checkSum += b;
                }
                // 每次没有窃取到task的时候,都会k+1(k值不会超过m),当k遍历了一圈还没有steal到任务,则当前小偷worker是过剩的,则inactive这个小偷worker
                if ((k = (k + 1) & m) == origin) {    // continue until stable
                  	// oldSum == (oldSum = checkSum) 实际上就是 oldSum == checkSum , oldSum = checkSum
                    // oldSum == checkSum 是判断 这个周期和上个周期 的base和是否一直,如果一直, 说明base可能没有变过
                    if ((ss >= 0 || (ss == (ss = w.scanState))) &&
                        oldSum == (oldSum = checkSum)) {
                        if (ss < 0 || w.qlock < 0)    // already inactive
                            break;
                        int ns = ss | INACTIVE;       // try to inactivate
                        long nc = ((SP_MASK & ns) |
                                   (UC_MASK & ((c = ctl) - AC_UNIT)));
                        // 维护 队列的 stack,可以指向前一个栈顶的队列
                        w.stackPred = (int)c;         // hold prev stack top
                        U.putInt(w, QSCANSTATE, ns);
                        if (U.compareAndSwapLong(this, CTL, c, nc))
                            ss = ns;
                        else
                            w.scanState = ss;         // back out
                    }
                    checkSum = 0;
                }
            }
        }
        return null;
    }

7、signalWork

    final void signalWork(WorkQueue[] ws, WorkQueue q) {
        long c; int sp, i; WorkQueue v; Thread p;
        // AC是负数,所以 active worker不足
        while ((c = ctl) < 0L) {                       // too few active
            // sp:第一位是0,没有版本号,没有inactive的worker
            if ((sp = (int)c) == 0) {                  // no idle workers
                //tc: tc不为0,就是代表 total worker - parallelism < 0, 所以需要添加worker
                if ((c & ADD_WORKER) != 0L)            // too few workers
                    tryAddWorker(c);
                break;
            }
            if (ws == null)                            // unstarted/terminated
                break;
            // 取栈顶的worker,如果下标已经越界或queue为null,线程池都是终止了
            if (ws.length <= (i = sp & SMASK))         // terminated
                break;
            if ((v = ws[i]) == null)                   // terminating
                break;
            // 新的scanState,版本+1,设置状态为激活,INACTIVE = 1 << 31,~INACTIVE = 01111111....
            int vs = (sp + SS_SEQ) & ~INACTIVE;        // next scanState
            // 确认 worker的 sp没有变化
            int d = sp - v.scanState;                  // screen CAS
            // 生成新的 ctl,(UC_MASK & (c + AC_UNIT))设置 高32位, (SP_MASK & v.stackPred)设置低32位
            long nc = (UC_MASK & (c + AC_UNIT)) | (SP_MASK & v.stackPred);
            if (d == 0 && U.compareAndSwapLong(this, CTL, c, nc)) {
                //激活worker
                v.scanState = vs;                      // activate v
                if ((p = v.parker) != null)
                    U.unpark(p);
                break;
            }
            //当前queue没有task 需要执行了,则停止signal
            if (q != null && q.base == q.top)          // no more work
                break;
        }
    }

parallelStream的注意事项

  1. parallelStream.forEach是不保证顺序的,如果要保证顺序正确,则使用 forEachOrdered。在这里插入图片描述
    在这里插入图片描述
  2. parallelStream实际上使用的是多线程的方式,所以在运行的业务逻辑里面要注意线程安全的问题
    在这里插入图片描述
  • 3
    点赞
  • 19
    收藏
    觉得还不错? 一键收藏
  • 6
    评论
当处理大数据量的计算时,你可以使用Fork/Join框架来实现并行计算。Fork/Join框架是Java提供的用于并行计算的工具,基于分治法的思想,可以将大任务划分成小任务,并且可以利用多个线程执行这些小任务。 下面是一个使用Fork/Join框架的示例代码: ```java import java.util.ArrayList; import java.util.List; import java.util.concurrent.ForkJoinPool; import java.util.concurrent.RecursiveAction; public class DataCalculation extends RecursiveAction { private static final int THRESHOLD = 100; // 每个任务处理的最小数据量 private List<String> regions; // 所有地区列表 public DataCalculation(List<String> regions) { this.regions = regions; } @Override protected void compute() { if (regions.size() <= THRESHOLD) { // 处理小规模计算任务 for (String region : regions) { // 根据6个条件计算数据 // TODO: 实现具体的计算逻辑 System.out.println("Calculating data for region: " + region); } } else { // 划分任务并创建子任务 int mid = regions.size() / 2; List<String> leftRegions = regions.subList(0, mid); List<String> rightRegions = regions.subList(mid, regions.size()); DataCalculation leftTask = new DataCalculation(leftRegions); DataCalculation rightTask = new DataCalculation(rightRegions); // 并行执行子任务 invokeAll(leftTask, rightTask); } } public static void main(String[] args) { List<String> regions = getRegions(); // 获取所有地区列表 ForkJoinPool forkJoinPool = new ForkJoinPool(); DataCalculation task = new DataCalculation(regions); forkJoinPool.invoke(task); forkJoinPool.shutdown(); } private static List<String> getRegions() { // TODO: 获取所有地区列表 return new ArrayList<>(); } } ``` 上述代码使用了Fork/Join框架来并行处理计算任务。当地区列表的大小小于等于阈值(THRESHOLD)时,直接在当前线程中处理计算任务;否则,将任务划分成两个子任务,分别处理左半部分和右半部分的地区列表,并且使用invokeAll()方法并行执行子任务。 请根据实际情况实现具体的计算逻辑和获取地区列表的方法。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 6
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值