ForkJoinPool源码全面解析

本文详细解析了ForkJoinPool的源码,包括ForkJoinPool、WorkQueue、ForkJoinWorkerThread和ForkJoinTask的内部结构和字段。重点介绍了ctl字段的构造、线程池的创建、任务提交、工作线程创建、任务执行流程及线程池的注销过程。此外,还讨论了CountedCompleter的特性及其在ForkJoinPool中的特殊处理。
摘要由CSDN通过智能技术生成

前言

前前后后花了几天时间才把这东西分析完,吐槽一下,这框架源码不是一般的难看,里面的变量名全是 a、j、k这种单个字母命名的,一不小心就容易看叉批了,里面的注释写了和没写差不多,不过好在代码不算多,最终还是慢慢看完了,不过这框架是真的强,有很多工作中可以借鉴的地方。

想要完全看懂ForkJoinPool源码的同学,只需要对位运算和java的Unsafe类有点了解,基本的东西我不会在文章中讲解(不会的这里👉谷歌),如果只是想看大体流程,只需要看我注解就行,我也会画些图来帮助你理解,希望可以帮到你!!


提示:本篇文章不讲框架如何使用,只对源码进行解析,源码使用1.8版本,以上版本略有不同

一、前奏

在看源码之前,大家还是要先了解一下ForkJoinPool框架一些设计思想和框架如何对线程进行控制的,这些都有助于尽快看懂源码。

ForkJoinPool框架由三大类构成:ForkJoinPool、ForkJoinWorkerThread和ForkJoinTask
这三个类组成了该框架的主体,下面也会分别对他们的要点进行讲解。

提示:下面这些字段介绍有些你可能看不懂,写出来主要是在后面用到了你可以返回来看

1. ForkJoinPool

MarkaQ
该类继承自AbstractExecutorService,和ThreadPoolExecutor一样具有submit、execute等方法,也就是说目前java拥有两种线程池,一种是ThreadPoolExecutor争抢式的,还有一种就是ForkJoinPool这种窃取式的线程池。ForkJoinPool设计出来就是因为原有的ThreadPoolExecutor对多核cpu利用率不够充分,这也使得在处理大量数据时ForkJoinPool比ThreadPoolExecutor执行速度上快了不少,当然,使用难度上也增加了不少。

下面对ForkJoinPool一些字段进行简单介绍,最好记住

1.1 ctl

volatile long ctl;

MarkaQ
这个字段是用来控制整个线程池运作的,我们先来看看ForkJoinPool类的构造方法

public ForkJoinPool() {
   		//无参
        this(Math.min(MAX_CAP, Runtime.getRuntime().availableProcessors()),
             defaultForkJoinWorkerThreadFactory, null, false);
}
    
public ForkJoinPool(int parallelism) {
   		//有参,parallelism在无参时取的是cpu核数
        this(parallelism, defaultForkJoinWorkerThreadFactory, null, false);
}
    
public ForkJoinPool(int parallelism,
                    ForkJoinWorkerThreadFactory factory,
                    UncaughtExceptionHandler handler,
                    boolean asyncMode) {
   
    this(checkParallelism(parallelism),
         checkFactory(factory),
         handler,
         asyncMode ? FIFO_QUEUE : LIFO_QUEUE,			
         "ForkJoinPool-" + nextPoolId() + "-worker-");
    checkPermission();
}

private static int checkParallelism(int parallelism) {
   
    if (parallelism <= 0 || parallelism > MAX_CAP)		//0 < parallelism <= MAX_CAP = 0x7fff = 32767
        throw new IllegalArgumentException();
    return parallelism;
}

private ForkJoinPool(int parallelism,
                     ForkJoinWorkerThreadFactory factory,
                     UncaughtExceptionHandler handler,
                     int mode,
                     String workerNamePrefix) {
   
    this.workerNamePrefix = workerNamePrefix;
    this.factory = factory;
    this.ueh = handler;
    this.config = (parallelism & SMASK) | mode;
    long np = (long)(-parallelism); // offset ctl counts
    this.ctl = ((np << AC_SHIFT) & AC_MASK) | ((np << TC_SHIFT) & TC_MASK);
}

▲ parallelism(并行度):让线程池达到指定的并行数,也是线程池要创建的线程数,默认为cpu核数,具体参数设置还得看业务场景


▲ config:parallelism不单独做一个字段储存,而是和线程池的工作模式通过位运算拼成一个字段config 前面说过构造方法对parallelism是有限制的,最大不能超过0x7fff
config = (parallelism & SMASK) | mode
parallelism最大:0000 0000 0000 0000   0111 1111 1111 1111
SMASK		    0000 0000 0000 0000   1111 1111 1111 1111
LIFO_QUEUE		0000 0000 0000 0000    0000 0000 0000 0000
FIFO_QUEUE		0000 0000 0000 0001    0000 0000 0000 0000

可以看出来,用config & SMASK就可以取出parallelism,用config & ~SMASK就可以判断是什么模式


▲ mode:模式是对工作队列的不同工作方式进行划分,有两种模式LIFO_QUEUE和FIFO_QUEUE,他们的值分别为 0 和 1 << 16

可以看到构造方法内第37行parallelism取的是负数,在ctl字段创建时parallelism取的是负值,我们以cpu4核为例:

parallelism=4;
np = (long)(-parallelism);
ctl = ((np << AC_SHIFT) & AC_MASK) | ((np << TC_SHIFT) & TC_MASK); 
-------------------------------------------------------------------------------------------------------------------
np << AC_SHIFT	  1111 1111 1111 1100 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000
															&
AC_MASK  		  1111 1111 1111 1111 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000
-------------------------------------------------------------------------------------------------------------------
np << TC_SHIFT	  1111 1111 1111 1111 1111 1111 1111 1100 0000 0000 0000 0000 0000 0000 0000 0000
															&
TC_MASK	 	 	  0000 0000 0000 0000 1111 1111 1111 1111 0000 0000 0000 0000 0000 0000 0000 0000
-------------------------------------------------------------------------------------------------------------------
(np << AC_SHIFT) & AC_MASK)		1111 1111 1111 1100 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000
	 														  |
((np << TC_SHIFT) & TC_MASK)	0000 0000 0000 0000 1111 1111 1111 1100 0000 0000 0000 0000 0000 0000 0000 0000
-------------------------------------------------------------------------------------------------------------------
  ctl = 1111 1111 1111 1100   1111 1111 1111 1100    0000 0000 0000 0000   0000 0000 0000 0000

看最后结果就可以看出来,这些第一眼看起来很复杂的操作,其实就是将 -parallelism 低16位分别放进ctl字段的高16位和随后的16位,ctl位long类型,共有64位,目前低32位为0,还没用到,等用到时再进行讲解。

再看我最上面画的图就能理解第64位和48位为什么是符号位了,ctl内parallelism 存的是他的负数,高32位的高16位代表的是活跃线程数(Active counts),高32位的低16位代表的是总线程数( Total counts),都存的是负数,可以理解为还有多少线程可以创建,在运行过程中,每创建一个线程都会让活跃线程数和总线程数加一,当第48位符号位变零,也就是变成正数了,到那时就证明线程创建满了,ForkJoinPool代码中就是判断该位来决定是否继续创建线程。

1.2 runState

volatile int runState;

MarkaQ
这是ForkJoinPool对线程池运行进行控制的一个字段,高三位是用户需要停止线程池时调用shutdown方法,32位被标记为SHUTDOWN时,然后标记STOP,对线程池进行一系列安全注销操作,最后标记TERMINATED。

  • STARTED:线程池已初始化后标记
  • RSLOCK :有时是状态锁,有时用作全局锁,主要避免一些重要操作被打断
  • RSIGNAL :该位被标记证明有人在获取RSLOCK时进入了睡眠,需要唤醒他们

1.3 stealCounter

volatile AtomicLong stealCounter;

stealCounter会对所有的线程队列偷盗任务次数进行统计,每次线程队列的nsteals达到最大值stealCounter就进行累加,线程队列注销前也会对他进行累加,stealCounter还是runstate的锁

1.4 workQueues

volatile WorkQueue[ ] workQueues;

该字段存放着下面要介绍的字段WorkQueue,workQueues数组(队列数组)被分为两种WorkQueue,一个是包括0的偶数位索引(共享队列),一个是奇数位索引WorkQueue(有线程的队列,我称它为线程队列),他们区别如下:

  1. 偶数位索引队列用于存放外部线程推送进来的任务(外部线程:不属于线程池管理的线程),奇数位索引队列用于存放内部线程执行用户代码时产生的任务
  2. 偶数位索引队列不持有线程,奇数位索引队列持有线程

2. WorkQueue

WorkQueue是ForkJoinPool的一个内部静态类,也是ForkJoinPool的工作队列

final ForkJoinPool pool;
final ForkJoinWorkerThread owner;

  • pool:队列属于哪个线程池的
  • owner:队列属于哪个线程的

2.1 scanState

volatile int scanState;

MarkaQ
scanState该字段奇数位索引队列初始值为索引值,共享队列初始值第32位为1(为负数)

  • INACTIVE:队列被标记为INACTIVE,线程不再执行任务,准备开始进入阻塞
  • SS_SEQ :记录线程进入了几次阻塞,每次线程被唤醒版本加一
  • SCANNING:线程正在执行偷的或本地任务时,标记队列正在执行任务

2.2 stackPred

int stackPred;

stackPred主要存放上一个已经进入阻塞的线程队列,下图
MarkaQ
还记得ForkJoinPool的ctl字段低32位初始化时他没有赋值吗?我觉得我的图应该是画得一目了然的了,没错,你们猜错,ctl低32位存的是WorkQueue的scanState字段,而WorkQueue的stackPred存放的是下一个WorkQueue的scanState,这样连续下去,被Doug Lea称为栈的结构,之后的注释里提到的栈都是指这个玩意,不是指其他的操作数栈之类的。

2.3 nsteals

int nsteals;

当前线程队列对其他队列偷盗任务的次数

2.4 hint

int hint;

初始值:共享队列hint为外部线程生成的线程随机数(它就是个随机数,不懂也没关系),线程队列初始值就有规律了,第一个线程队列hint为0x9e3779b9,第二个为第一个hint加上0x9e3779b9,类推,这个值我猜是为了减少线程间执行时的碰撞,之后线程队列hint可能会发生改变,线程队列在执行任务时要等待另一个任务执行完毕时,该线程就会去找小偷,那时hint值可能为小偷在队列数组中的索引

2.5 config

int config;

共享队列:存放该队列在队列数组中索引,并将其32位标记为SHARED_QUEUE(1<<31),也就是负数
线程队列:存放该队列在队列数组中索引和队列的执行模式(LIFO_QUEUE或FIFO_QUEUE)

2.6 qlock

volatile int qlock;

MarkaQ

  • terminate:当前线程队列开始注销
  • locked:当前线程队列已锁,不允许推送任务

2.7 base&top

volatile int base;
int top;

我们先来看看WorkQueue的构造方法:

WorkQueue(ForkJoinPool pool, ForkJoinWorkerThread owner) {
   
	this.pool = pool;
	this.owner = owner;
	base = top = INITIAL_QUEUE_CAPACITY >>> 1;      //base=top=4096  INITIAL_QUEUE_CAPACITY=8192
}

可以看出base和top位初始值都为初始队列长度的一半,在存放任务时第一个任务存放在4096这个索引位(任务是存放在数组里的),当top超出索引位8191,继续推送任务时会进行模运算,回到索引位为0的位置
MarkaQ

2.8 currentJoin

volatile ForkJoinTask<?> currentJoin;

线程在执行用户代码时,有时需要等待另一个任务完成才能继续运行,currentJoin指的就是当前要等待哪个任务的结果

2.9 currentSteal

volatile ForkJoinTask<?> currentSteal;

currentSteal指当前线程从队列数组里偷来的那个任务,这个任务一执行完就将currentSteal置空

3. ForkJoinWorkerThread

MarkaQ

final ForkJoinPool pool;
final ForkJoinPool.WorkQueue workQueue;

ForkJoinWorkerThread继承自Thread类,那么他的起始方法就是run方法了,这里先不讲解。

ForkJoinWorkerThread就两个成员变量

  • pool:表示他属于哪个线程池的
  • workQueue:线程拥有的队列

4. ForkJoinTask

MarkaQ

ForkJoinTask实现了Future,自然有获取任务结果和状态的方法,这些就不介绍了,关键在他的三大子类RecursiveAction、RecursiveTask和CountedCompleter,在用ForkJoinPool线程池时写的任务也是去继承这三个类,而不会直接去继承ForkJoinTask,这三个类之中RecursiveAction和RecursiveTask是一类思想,只不过一个没返回值一个有返回值,而CountedCompleter是另一个方向,具体区别还是看了代码你才了解

4.1 status

volatile int status;

MarkaQ

  • NORMAL:任务正常完成
  • CANCELLED:任务被取消(也算完成)
  • EXCEPTIONAL:执行用户代码时抛出异常,异常完成
  • SIGNAL:为1时代表有线程正在等待该任务执行结果,需要将其唤醒

5. 大致流程

这是我在大致扫描源码时画的流程图,可能不太准确,不过应该也还能看,辛辛苦苦画的图不能浪费了😆
MarkaQ

二、序曲亦是终章

从此处开始,我们将开始进入源码的阅读,我们将从线程池的创建,到任务的提交,到线程的创建,再到任务全都执行完毕线程池的注销,以这样的流程开始讲解,中途肯定有大量函数的跳转,滚动条肯定是要滚起来的,这里先提示一下,在注释中有很多东西我对他的叫法可能大家不知道指的是啥,这里先列举出来一些来,还不懂可以留言

runState:运行状态、线程池运行状态
scanState:队列状态
status:任务状态,任务完成状态
WorkQueue:队列、任务队列、线程队列(奇数位)、共享队列(偶数位)
WorkQueue[] workQueues:队列数组
ForkJoinTask<?>[] array:任务数组
ForkJoinTask:任务
当前任务和目标任务:一般指函数传参进来的任务

这是一个Demo,数组里有一百万个随机数,计算他们的和,每个任务最多处理5万个数,最后汇总和输出,大家先看懂代码的意思,我们从入口开始一步步跟进

public class ForkJoinPoolDemo {
   
	static int[] nums = new int[1000000];
	static final int MAX_NUM = 50000;
	static Random r = new Random();
	
	static {
   
		for(int i=0; i<nums.length; i++) {
   
			nums[i] = r.nextInt(100);
		}
		System.out.println("---" + Arrays.stream(nums).sum()); //stream api
	}
	

	static class AddTaskRet extends RecursiveTask<Long> {
   
		
		private static final long serialVersionUID = 1L;
		int start, end;
		
		AddTaskRet(int s, int e) {
   
			start = s;
			end = e;
		}

		@Override
		protected Long compute() {
   
			
			if(end-start <= MAX_NUM) {
   
				long sum = 0L;
				for(int i=start; i<end; i++) sum += nums[i];
				return sum;
			} 
			
			int middle = start + (end-start)/2;
			
			AddTaskRet subTask1 = new AddTaskRet(start, middle);
			AddTaskRet subTask2 = new AddTaskRet(middle, end);
			subTask1.fork();
			subTask2.fork();

			return subTask1.join() + subTask2.join();
		}

	}
	
	public static void main(String[] args) throws IOException {
   
		ForkJoinPool fjp = new ForkJoinPool();
		AddTaskRet task = new AddTaskRet(0, nums.length);
		fjp.execute(task);
		long result = task.join();
		System.out.println(result);
	}
}

大家先看main方法,ForkJoinPool构造方法已经讲过了,忘了的可以回去看,这里我们从ForkJoinPool的execute方法开始

1. 任务提交

提示:这里只对每个方法进行总结,不会对每个字符都进行讲解,这篇文章只是给你看源码时参考用,位运算那些自己算了才有用,实在搞不懂看我注释可能让你想通,基本每行都有注释,我想看注释都应该知道大体流程了

execute

public void execute(ForkJoinTask<?> task) {
   
    if (task == null)
        throw new NullPointerException();
    externalPush(task);     //外部线程添加任务
}

这方法就调用了externalPush,没啥好讲的,直接看externalPush方法


externalPush
final void externalPush(ForkJoinTask<?> task) {
   
    WorkQueue[] ws; WorkQueue q; int m;
    int r = ThreadLocalRandom.getProbe();           //获取当前线程随机数
    int rs = runState;
    if ((ws = workQueues) != null && (m = (ws.length - 1)) >= 0 &&          //workQueues已初始化好
        (q = ws[m & r & SQMASK]) != null && r != 0 && rs > 0 &&             //随机偶数槽位存在,线程随机数存在
        U.compareAndSwapInt(q, QLOCK, 0, 1)) {
                           //对当前偶数位队列qlock字段加锁
        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;           //找到队列top位偏移
            U.putOrderedObject(a, j, task);                 //将当前任务推入队列top位
            U.putOrderedInt(q, QTOP, s + 1);                //top++
            U.putIntVolatile(q, QLOCK, 0);                  //队列解锁
            if (n <= 1)             //当前任务数小于等于一唤醒或创建线程(为尽快达到并行度,也可能所有线程都在阻塞等待任务)
                signalWork(ws, q);
            return;
        }
        U.compareAndSwapInt(q, QLOCK, 1, 0);        //解锁qlock
    }
    externalSubmit(task);           //对队列数组和队列进行初始化操作
}

基本位运算就不讲了,对照我画的图将数据带入计算就知道是啥操作了。

方法总结:externalPush代码不难,推送任务时先查看队列数组是否已初始化,当前外部线程随机数对应的偶数槽位共享队列是否已创建:如果都存在,将任务推送至该偶数索引位共享队列的top位,当前共享队列如果任务不多,就唤醒或创建线程;如果没初始化好就调用externalSubmit方法,这个方法可以说是externalPush的完整版方法
跳板:signalWork


externalSubmit
private void externalSubmit(ForkJoinTask<?> task) {
   
    int r;                                    // initialize caller's probe
    //他就是一个线程保存的随机数,不懂也没关系,你知道线程有个随机数就行
    if ((r = ThreadLocalRandom.getProbe()) == 0) {
             //初始化线程随机数
        ThreadLocalRandom.localInit();
        r = ThreadLocalRandom.getProbe();
    }
    for (;;) {
   
        WorkQueue[] ws; WorkQueue q; int rs, m, k;
        boolean move = false;
        if ((rs = runState) < 0) {
             //如果有人调用shutdown,则进入
            tryTerminate(false, false);     // help terminate  有人调用shutdown但如果线程池有任务时,只是标记shutdown状态,这里真正开始终止程序
            throw new RejectedExecutionException();
        }
        else if ((rs & STARTED) == 0 ||     // initialize    线程池还未启动过,也就是还未初始化
                 ((ws = workQueues) == null || (m = ws.length - 1) < 0)) {
   
            int ns = 0;
            rs = lockRunState();            //锁定运行状态
            try {
   
                if ((rs & STARTED) == 0) {
         //还未启动过
                    U.compareAndSwapObject(this, STEALCOUNTER, null,
                                           new AtomicLong());           //初始化stealcounter为atomicLong
                    // create workQueues array with size a power of two
                    int p = config & SMASK; // ensure at least 2 slots      获取并行度parallelism
                    int n = (p > 1) ? p - 1 : 1;
                    n |= n >>> 1; n |= n >>> 2;  n |= n >>> 4;
                    n |= n >>> 8; n |= n >>> 16; n = (n + 1) << 1;          //n最小为4,确保当前并行度下workQueues偶数槽位够用,p*2<2的次方
                    workQueues = new WorkQueue[n];
                    ns = STARTED;                   //运行状态置为已开启过
                }
            } finally {
   
                unlockRunState(rs, (rs & ~RSLOCK) | ns);    //解锁并设置runState为started
            }
        }
        else if ((q = ws[k = r & m & SQMASK]) != null) {
           //根据线程随机数计算的偶数位已创建队列,m为队列数组长度减一,队列数组长度为2的次方,
        //所以m是用来取模的,防止超出数组索引,SQMASK转换成二进制就知道他是将别人置为偶数的
            if (q.qlock == 0 && U.compareAndSwapInt(q, QLOCK, 0, 1)) {
     //队列没有其他线程已上锁,且加锁成功
                ForkJoinTask<?>[] a = q.array;
                int s = q.top;
                boolean submitted = false; // initial submission or resizing
                try {
                         // locked version of push
                    if ((a != null && a.length > s + 1 - q.base) ||             //任务数组不为空且还有空位,否则进行扩容
                        (a = q.growArray()) != null) {
   
                        int j = (((a.length - 1) & s) << ASHIFT) + ABASE;      //获取top偏移
                        U.putOrderedObject(a, j, task);         //添加任务至top位
                        U.putOrderedInt(q, QTOP, s + 1);       //top++
                        submitted = true;
                    }
                } finally {
   
                    U.compareAndSwapInt(q, QLOCK, 1, 0);        //解锁队列
                }
                if (submitted) {
           //任务添加成功
                    signalWork(ws, q);  //创建线程
                    return;
                }
            }
            move = true;                   // move on failure
        }
        else if (((rs = runState) & RSLOCK) == 0) {
    // create new queue  运行状态未上锁
            q = new WorkQueue(this, null);      //创建任务队列,不持有线程
            q.hint = r;     //设置任务队列hint为当前线程随机数
            q.config = k | SHARED_QUEUE;    //当前数组索引位数字第32位置为1,共享为负数
            q.scanState = INACTIVE;         //共享线程scanState为负数
            rs = lockRunState();           // publish index 锁运行状态
            if (rs > 0 &&  (ws = workQueues) != null &&     //队列数组已初始化好
                k < ws.length && ws[k] == null)             //当前偶数位队列不存在
                ws[k] = q;                 // else terminated
            unlockRunState(rs, rs & ~RSLOCK);       //解锁
        }
        else
            move = true;                   // move if busy
        if (move)
            r = ThreadLocalRandom.advanceProbe(r);  //重置线程随机数
    }
}

方法总结:队列数组还未初始化时,就进入循环,开始创建一个共享队列出来。先创建队列数组,队列数组长度为2的次方,然后进入下次循环,创建当前外部线程随机数对应的偶数槽位共享队列,共享队列各个字段赋值这里大家应该能看懂了,创建好这个共享队列就可以往里边推送任务了,这个共享队列还没初始化任务数组,调用growArray来初始化任务数组,然后就可以返回了
跳板:tryTerminatelockRunStatesignalWork


growArray
final ForkJoinTask<?>[] growArray() {
   
    ForkJoinTask<?>[] oldA = array;         //获取当前任务数组
    int size = oldA != null ? oldA.length << 1 : INITIAL_QUEUE_CAPACITY;    //老数组不为空,用初始化容量,否则容量翻倍
    if (size > MAXIMUM_QUEUE_CAPACITY)      //新数组长度不大于67108864  2^26
        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;       //获取老数组base偏移
            int j    = ((b &    mask) << ASHIFT) + ABASE;       //获取新数组base偏移
            x = (ForkJoinTask<?>)U.getObjectVolatile(oldA, oldj);   //从base开始取出老数组的任务
            if (x != null &&
                U.compareAndSwapObject(oldA, oldj, x, null))     //老数组删除任务
                U.putObjectVolatile(a, j, x);           //新数组添加任务
        } while (++b != t);     //base++
    }
    return a;
}

方法总结:数组还没创建时就用初始容量(8192),直接new一个ForkJoinTask数组,然后返回;数组已存在,将原数组容量翻倍(最大容量不超过2^26),也是new一个出来,原数组里还有任务存在就循环将他迁移至新数组


fork

public final ForkJoinTask<V> fork() {
   
    Thread t;
    if ((t = Thread.currentThread()) instanceof ForkJoinWorkerThread)
        ((ForkJoinWorkerThread)t).workQueue.push(this);     //内部线程,放入当前线程队列里
    else
        ForkJoinPool.common.externalPush(this);     //外部线程,放入共享线程池里
    return this;
}

final void push(ForkJoinTask<?> task) {
   
    ForkJoinTask<?>[] a; ForkJoinPool p;
    int b = base, s = top, n;
    if ((a = array) != null) {
       // ignore if queue removed
        int m = a.length - 1;     // fenced write for task visibility
        U.putOrderedObject(a, ((m & s) << ASHIFT) + ABASE, task);   //将任务推入当前top位
        U.putOrderedInt(this, QTOP, s + 1);         //top++
        if ((n = s - b) <= 1) {
            //队列之前任务数小于等于一个时,创建或唤醒线程(这样设计应该是为了尽快达到并行度)
            if ((p = pool) != null)
                p.signalWork(p.workQueues, this);
        }
        else if (n >= m)        //达到数组上限,扩容
            growArray();
    }
}

方法总结:

  • 内部线程,扔进当前线程队列top位
  • 外部线程,通过externalPush方法放入共享线程池的队列里

2. 创建工作线程

前面外部线程将共享队列创建出来,并推送任务进这个共享队列,但目前还没有线程来执行这个任务,下面开始创建线程来执行任务

signalWork

final void signalWork(WorkQueue[] ws, WorkQueue q) {
   
    long c; int sp, i; WorkQueue v; Thread p;
    while ((c = ctl) < 0L) {
                          // 活跃线程数未达到并行度时ctl都小于零
        if ((sp = (int)c) 
  • 6
    点赞
  • 16
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值