ForkJoinPool中submit处理流程

细节不详细介绍,具体可参考http://www.infoq.com/cn/articles/fork-join-introduction。本文主要分析的submit处理流程,示例代码:

package com.company.Chapter06;


import java.util.concurrent.ExecutionException;
import java.util.concurrent.Future;


public class CountTask extends RecursiveTask<Integer> {
    private static final int THEADHOLD = 2;//阈值
    private int start;
    private int end;

    public CountTask(int start, int end) {
        this.start = start;
        this.end = end;
    }

    protected Integer compute() {
        int sum = 0;
        boolean canCompute = (end - start) <= THEADHOLD;
        if (canCompute) {
            for (int i = start; i <= end; i++) {
                sum += i;
            }
        } else {
            int middle = (start + end) / 2;
            CountTask left = new CountTask(start, middle);
            CountTask right = new CountTask(middle + 1, end);
            left.fork();
            right.fork();
            int leftSum = left.join();
            int rightSum = right.join();
            sum = leftSum + rightSum;
        }
        return sum;
    }

    public static void main(String[] args) throws ExecutionException, InterruptedException {
        ForkJoinPool forkJoinPool=new ForkJoinPool();
        CountTask countTask=new CountTask(1,4);
        Future<Integer> result=forkJoinPool.submit(countTask);
        System.out.println(result.get());
    }
}

submit流程图:


<think>我们正在讨论ForkJoinPool的工作队列机制。根据引用[4]和[5],ForkJoinPool内部使用工作窃取队列(WorkQueue)来管理任务。每个工作线程(ForkJoinWorkerThread)都有一个自己的双端队列(Deque),线程从自己队列的头部取任务执行,而其他空闲线程可以从其他队列的尾部窃取任务执行。这种机制可以充分利用多核处理器的能力,减少线程间的竞争。 下面我将详细解释ForkJoinPool如何使用工作队列: ### 1. 工作队列的基本结构 ForkJoinPool内部维护一个WorkQueue数组(`WorkQueue[] workQueues`),每个工作线程在初始化时会被分配一个工作队列。工作队列是一个数组实现的双端队列,支持以下操作: - **push**:工作线程将新任务放入自己队列的头部(`top`端),此操作仅由工作线程自己执行,无需同步。 - **pop**:工作线程从自己队列的头部取出任务执行,同样无需同步。 - **poll**:其他线程(窃取者)从队列的尾部(`base`端)窃取任务,此操作需要CAS操作以保证并发安全。 引用[5]提到,队列的三个操作: - 工作线程自己操作队列头部(单线程操作) - 其他工作线程操作队列尾部(多线程操作,需要CAS) ### 2. 工作窃取(Work-Stealing)过程 当一个工作线程的任务执行完后,它会尝试从其他工作线程的队列尾部窃取任务。过程如下: 1. 工作线程先检查自己的队列是否为空,如果为空,则随机选择一个其他工作线程的队列,尝试从尾部(`base`)窃取一个任务。 2. 窃取操作通过CAS增加`base`索引来实现,如果成功则获得该任务。 3. 如果所有其他队列都为空,则该工作线程可能会进入休眠状态,等待新任务的到来。 ### 3. 任务提交与执行 - **外部提交任务**:当外部线程(非ForkJoinPool工作线程)提交任务时,ForkJoinPool会随机选择一个工作队列,将任务放入该队列的尾部(通过`externalPush`方法)。 - **内部提交任务**:工作线程在执行过程中产生新的子任务(通过`fork()`方法),则将该子任务放入自己的队列头部(`push`操作)。 ### 4. 代码示例:工作队列操作 以下是一个简单的ForkJoinPool使用示例,展示了任务如何被分解和窃取: ```java import java.util.concurrent.*; public class ForkJoinDemo extends RecursiveTask<Long> { private final long start; private final long end; private static final long THRESHOLD = 10000; // 阈值 public ForkJoinDemo(long start, long end) { this.start = start; this.end = end; } @Override protected Long compute() { long length = end - start; if (length <= THRESHOLD) { // 直接计算 long sum = 0; for (long i = start; i <= end; i++) { sum += i; } return sum; } else { // 分解任务 long middle = start + (end - start) / 2; ForkJoinDemo left = new ForkJoinDemo(start, middle); ForkJoinDemo right = new ForkJoinDemo(middle + 1, end); // 将left任务推入当前线程队列(push操作) left.fork(); // 递归执行right任务(当前线程) long rightResult = right.compute(); // 等待left任务完成(可能被其他线程窃取执行) long leftResult = left.join(); return leftResult + rightResult; } } public static void main(String[] args) { ForkJoinPool pool = new ForkJoinPool(); ForkJoinDemo task = new ForkJoinDemo(0, 1000000); long result = pool.invoke(task); System.out.println("Result: " + result); } } ``` ### 5. 工作队列的源码关键点(参考引用[5]) - `volatile int base;`:队列尾部的索引,用于窃取操作(volatile保证可见性)。 - `int top;`:队列头部的索引,非volatile,因为只有工作线程自己修改。 - 队列扩容:当队列满时,会创建一个新的数组(原数组的两倍)并将旧数组中的任务复制过去。 ### 6. 使用注意事项 1. **避免阻塞操作**:工作线程被阻塞会影响整个池的性能,因为其他线程可能依赖该线程队列中的任务(引用[1])。 2. **任务分解**:合理设置阈值,避免任务过小导致过多的任务分解和调度开销。 3. **任务类型**:最适合计算密集型任务,对于I/O密集型任务,建议使用`ManagedBlocker`(引用[1])。 ### 总结 ForkJoinPool通过工作窃取队列(WorkQueue)实现了高效的任务调度。每个工作线程优先处理自己队列中的任务,当空闲时从其他线程的队列尾部窃取任务。这种机制能够充分利用多核处理器,减少线程间的竞争,提高并行计算效率[^4][^5]。 §§相关问题§§ 1. 如何监控ForkJoinPool的工作队列状态? 2. 工作窃取算法在什么情况下效率最高? 3. ForkJoinPool与普通线程池(如ThreadPoolExecutor)在任务调度上有何本质区别? 4. 如何避免在ForkJoinPool中产生过多的子任务? 5. ForkJoinPool的异常处理机制是怎样的?</think>### ForkJoinPool 工作队列机制详解 ForkJoinPool 使用特殊的工作窃取队列(Work-Stealing Queue)实现高效的任务调度,这是其核心优化机制。以下是详细工作原理和使用方法: #### 一、工作队列结构 1. **双端队列设计** - 每个工作线程维护自己的双端队列(Deque) - 队列头部(`top`端):线程**自己操作**(LIFO) - 队列尾部(`base`端):**其他线程窃取**(FIFO) ```java volatile int base; // 尾部索引(volatile保证可见性) int top; // 头部索引(非volatile) ForkJoinTask<?>[] array; // 任务存储数组 ``` 2. **操作类型** - **push()**:线程将新任务放入**自己队列头部** ```java void push(ForkJoinTask<?> task) { int t = top; array[t] = task; // 无锁操作 top = t + 1; // 仅线程自己修改top } ``` - **pop()**:线程从**自己队列头部**取任务 - **poll()**:其他线程从**队列尾部窃取**任务(需CAS操作) #### 二、工作窃取流程 1. **任务执行优先级** ```mermaid graph LR A[工作线程] --> B{自己队列有任务?} B -->|是| C[从头部pop任务执行] B -->|否| D[随机选其他队列] D --> E{从尾部poll成功?} E -->|是| F[执行窃取的任务] E -->|否| G[尝试其他队列/等待] ``` 2. **窃取实现代码** ```java ForkJoinTask<?> poll() { int b = base; int t = top; ForkJoinTask<?>[] a = array; if (b != t) { // 队列非空 ForkJoinTask<?> task = a[b]; if (task != null && UNSAFE.compareAndSwapObject(a, offset, task, null)) { base = b + 1; // CAS更新base索引 return task; } } return null; } ``` #### 三、队列使用实践 1. **任务提交方式** - **外部提交**:外部线程调用`pool.submit(task)` ```java ForkJoinPool pool = new ForkJoinPool(4); pool.submit(new MyTask()); // 任务进入随机工作队列 ``` - **内部fork**:工作线程内调用`task.fork()` ```java protected Integer compute() { MyTask left = new MyTask(...); left.fork(); // 任务压入当前线程队列头部 // ...继续处理其他任务 } ``` 2. **队列扩容机制** - 当队列满时自动扩容(2倍增长) - 扩容过程: ```java void growArray() { ForkJoinTask<?>[] old = array; int size = old.length; ForkJoinTask<?>[] newArray = new ForkJoinTask<?>[size * 2]; System.arraycopy(old, 0, newArray, 0, size); array = newArray; // 更新引用 } ``` #### 四、性能优化建议 1. **任务粒度控制** - 设置合理阈值避免过度分解 ```java class MyTask extends RecursiveTask<Integer> { private static final int THRESHOLD = 1000; // 分解阈值 protected Integer compute() { if (data.length < THRESHOLD) { return directCompute(); // 直接计算 } // 否则分解任务... } } ``` 2. **避免阻塞操作** - I/O操作应配合`ManagedBlocker` ```java class BlockingTask implements ForkJoinPool.ManagedBlocker { public boolean block() throws InterruptedException { // 执行阻塞操作 return true; } public boolean isReleasable() { return false; } } ``` 3. **队列监控方法** ```java // 查看工作队列状态 System.out.println(pool.toString()); // 输出: ForkJoinPool.commonPool-worker-1[0, 0, 0] ... ``` #### 五、与普通线程池对比 | **特性** | **ForkJoinPool** | **ThreadPoolExecutor** | |------------------|--------------------------------|-----------------------------| | 队列结构 | 工作窃取双端队列 | 阻塞单端队列 | | 任务获取策略 | 优先本地队列,窃取其他队列 | 全局队列竞争 | | 适用场景 | 递归分治任务 | 独立任务 | | 线程利用率 | 更高(减少空闲) | 可能产生饥饿现象 | | 任务调度开销 | 更低(无全局锁竞争) | 存在队列锁竞争 | > **关键优势**:工作窃取机制能有效平衡负载,当线程完成自己队列的任务后,可"窃取"其他线程队列尾部的任务,减少线程空闲时间[^4][^5]。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值