ForkJoinPool 偷任务

深入解析ForkJoinPool中ForkJoinWorkerThread的run()方法,揭示其如何初始化任务队列,通过scan()方法从其他线程窃取任务的详细过程,以及在无任务时的处理策略。

ForkJoinPool中的工作线程ForkJoinWorkerThread的run()方法是这样的。

public void run() {
    if (workQueue.array == null) { // only run once
        Throwable exception = null;
        try {
            onStart();
            pool.runWorker(workQueue);
        } catch (Throwable ex) {
            exception = ex;
        } finally {
            try {
                onTermination(exception);
            } catch (Throwable ex) {
                if (exception == null)
                    exception = ex;
            } finally {
                pool.deregisterWorker(this, exception);
            }
        }
    }
}

这在其中直接调用了ForkJoinPool的runWorker()方法。由此可见,ForkJoinPool中的工作线程主要逻辑在其中。

final void runWorker(WorkQueue w) {
    w.growArray();                   // allocate queue
    int seed = w.hint;               // initially holds randomization hint
    int r = (seed == 0) ? 1 : seed;  // avoid 0 for xorShift
    for (ForkJoinTask<?> t;;) {
        if ((t = scan(w, r)) != null)
            w.runTask(t);
        else if (!awaitWork(w, r))
            break;
        r ^= r << 13; r ^= r >>> 17; r ^= r << 5; // xorshift
    }
}

首先,会调用workQueue的growAway()方法初始化任务队列数组或是扩容。此处的作用自然是初始化。

final ForkJoinTask<?>[] growArray() {
    ForkJoinTask<?>[] oldA = array;
    int size = oldA != null ? oldA.length << 1 : INITIAL_QUEUE_CAPACITY;
    if (size > MAXIMUM_QUEUE_CAPACITY)
        throw new RejectedExecutionException("Queue capacity exceeded");
    int oldMask, t, b;
    ForkJoinTask<?>[] a = array = new ForkJoinTask<?>[size];
    if (oldA != null && (oldMask = oldA.length - 1) >= 0 &&
        (t = top) - (b = base) > 0) {
        int mask = size - 1;
        do { // emulate poll from old array, push to new array
            ForkJoinTask<?> x;
            int oldj = ((b & oldMask) << ASHIFT) + ABASE;
            int j    = ((b &    mask) << ASHIFT) + ABASE;
            x = (ForkJoinTask<?>)U.getObjectVolatile(oldA, oldj);
            if (x != null &&
                U.compareAndSwapObject(oldA, oldj, x, null))
                U.putObjectVolatile(a, j, x);
        } while (++b != t);
    }
    return a;
}

如果一开始初始化,那么只是简单的建立了一个数组并赋给array便宣告结束。而如果是扩容,那么则会新建一个两倍大小的数组,并从原数组以base为起点到top为止的数据复制到新数组中。

 

之后便是通过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
        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) {
                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) {
                        if (ss >= 0) {
                            if (U.compareAndSwapObject(a, i, t, null)) {
                                q.base = b + 1;
                                if (n < -1)       // signal others
                                    signalWork(ws, q);
                                return t;
                            }
                        }
                        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;
            }
            if ((k = (k + 1) & m) == origin) {    // continue until stable
                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)));
                    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;
}

Scan()方法分为两部分,先看第一部分。

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) {
        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) {
                if (ss >= 0) {
                    if (U.compareAndSwapObject(a, i, t, null)) {
                        q.base = b + 1;
                        if (n < -1)       // signal others
                            signalWork(ws, q);
                        return t;
                    }
                }
                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;
    }

根据执行偷任务的线程的随机数索引,这里该索引下线程池工作队列数组中的目标工作队列,如果不为空,且base小于top说明该队列中还有没处理完的任务,那么就尝试从这里偷取任务。

这里根据base的值计算偏移量得到数组中索引最小的任务,而后将这个任务从当前队列中取出,如果取出了之后,目标队列仍旧有任务剩余,那么将会调用signalWorker()方法尝试重新添加一个工作线程或者唤醒一个工作线程,在此之后返回取得到的任务代表依次成功的偷任务结束。

如果在取得到任务后,该线程的scanState如果小于0也就是处于不活跃状态,那么将会通过tryRealease()重新唤起一个线程。

如果从base出处没有取得任务,或是被别的线程已经取走,那么将会重新计算随机值,在下一个循环中换一个随机位置上的工作队列去尝试偷任务。

 

如果随机数定位到的队列中没有可以偷取的任务(任务队列中的可执行任务为空),那么将会记录当前队列的base值,之后在下一个部分。

if ((k = (k + 1) & m) == origin) {    // continue until stable
            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)));
                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;

如果此时扫描完工作队列数组,并且仍然没有找到任务,那么就会将这个队列设置为不活跃。

这里首先会去给线程池的状态量中的活跃线程数量减一,再把当前线程的scanState置为不活跃状态。此时,也代表该线程的偷任务的结束。

### ForkJoinPool 线程池概述 ForkJoinPool 是一种特殊的线程池,旨在支持分治算法的并行化操作。这种线程池特别适用于可以分解成多个子任务的工作负载,在处理大量细粒度的任务时表现出色[^1]。 #### 创建与初始化 创建 `ForkJoinPool` 的实例可以通过多种方式完成: ```java // 使用默认参数创建一个新的 ForkJoinPool 实例 ForkJoinPool customThreadPool = new ForkJoinPool(); // 或者指定并行级别来控制最大工作线程数 int parallelismLevel = Runtime.getRuntime().availableProcessors(); customThreadPool = new ForkJoinPool(parallelismLevel); ``` 对于大多数应用来说,默认情况下会自动利用公共的共享线程池 `ForkJoinPool.commonPool()` 来运行异步任务,除非有特殊需求才需自定义配置新实例[^2]。 #### 主要组件介绍 该框架主要包括以下几个核心部分: - **ForkJoinPool**: 负责管理一组工作线程,并调度它们执行提交给它的任务。 - **ForkJoinTask<T>**: 表示可被分割成更小子任务的大任务抽象类;它有两个重要子类 RecursiveAction 和 RecursiveTask,分别对应无返回值和带返回值的情况。 - **ForkJoinWorkerThread**: 执行由 ForkJoinPool 提交的具体任务的实际工作者线程[^3]. #### 关键机制解析 ##### 工作窃取 (Work Stealing) 这是使 ForkJoinPool 效率高的一个重要特性之一。当某个线程完成了自己的所有分配到的任务之后,它可以去其他忙碌中的线程那里“走”一部分未完成的任务来进行处理,从而提高了整体资源利用率和吞吐量。 ##### 任务拆解与合并 为了充分利用多核处理器的能力,大任务会被递归地划分为越来越小的部分直到达到一定阈值为止,这些较小单位可以直接被执行而不再进一步细分。一旦各个分支都已完成计算,则通过逆向遍历树形结构逐步汇总结果。 #### 应用场景举例 最常见的应用场景是在 Stream API 中调用了 `parallelStream()` 方法后的内部实现上采用了此线程池作为后台支撑。另外,“虚拟线程”的概念也依赖于此类线程池得以高效运作。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值