Java线程池ThreadPoolExecutor中execute()方法原理

环境

Java:1.8

前言

线程池之前知是知道,但是印象不是很深刻,今天看了下源码,发现源码很好理解,所以记录下;


线程池

要了解线程池,首先要了解线程池里面的状态控制变量ctl

  • 线程池的ctl是一个原子的AtomicInteger
  • 这个ctl包含两个参数:
    • runState 线程的状态
    • workerCount 激活的线程数
  • 它的低29位用于存放当前的线程数,因此一个线程池在理论上最大的线程数是(2^29)-1;高3位是用于表示当前线程池的状态,其中高三位的值和状态对于如下:
    • 111: RUNNING
    • 000: SHUTDOWN
    • 001: STOP
    • 010: TIDYING
    • 011: TERMINATED
      为了能够使用ctl,线程池提供了三个方法:
    // Packing and unpacking ctl
    //获取线程池的状态
    private static int runStateOf(int c)     { return c & ~CAPACITY; }
    //获取线程池中工作线程数量
    private static int workerCountOf(int c)  { return c & CAPACITY; }
    //根据线程池的状态和工作线程数得到ctl
    private static int ctlOf(int rs, int wc) { return rs | wc; }

execute

public void execute(Runnable command) {
        if (command == null)
            throw new NullPointerException();
     
        int c = ctl.get();
        //如果工作线程数小于核心线程数,
        if (workerCountOf(c) < corePoolSize) {
        	//执行addWorker,会创建一个核心线程,如果创建失败,重新获取ctl
            if (addWorker(command, true))
                return;
            c = ctl.get();
        }
        //如果工作线程数大于等于核心线程数,线程池的状态是RUNNING,并且可以添加进队列
        //(RUNNING状态下)如果添加失败,说明是队列已经满了,接着就去创建新的线程,如果大于最大线程数,则执行拒绝策略
        //如果线程池不是RUNNING状态,则执行拒绝策略(当然还会调addWorker进行判断一次)
        if (isRunning(c) && workQueue.offer(command)) {
        	//再次获取ctl,进行双重检索(也就是对线程池的状态再次检查一遍)
            int recheck = ctl.get();
            //如果线程池是不是处于RUNNING的状态,那么就会将任务从队列中移除,
            //如果移除失败,则会判断工作线程是否为0 ,如果过为0 就创建一个非核心线程
            //如果移除成功,就执行拒绝策略,因为线程池已经不可用了;
            if (! isRunning(recheck) && remove(command))
                reject(command);
            else if (workerCountOf(recheck) == 0)
                addWorker(null, false);
        }
        else if (!addWorker(command, false))
	        //线程池挂了或者大于最大线程数
            reject(command);
    }

通过上面的代码我们可以得出几个结论:
① 当工作线程数小于核心线程数时,会创建核心线程数;
②如果工作线程数大于等于核心线程数时,会尝试将任务添加进队列;

  • 如果成功,会对线程池的状态进行二次验证(因为可能存在刚好线程池的状态发生了改变的情况),只要是RUNNING的状态,就一定要保证有工作线程还在。
    ③ 二次验证时,如果线程池不是处于RUNNING的状态,那么就会将任务从队列中移除;

    • 如果移除失败,则会判断工作线程是否为0 ,如果过为0 就创建一个非核心线程;
    • 如果移除成功,就执行拒绝策略,因为线程池已经不可用了;

    ④ 二次验证时,如果线程池处于RUNNING的状态,会判断工作线程是否为0 ,如果过为0 就创建一个非核心线程;

  • 如果失败,说明是队列已经满了,接着就去创建新的线程,如果大于最大线程数,则执行拒绝策略


对于上面的代码我自己有个小小的疑问:
1、代码中addWorker方法,总共出现3次,第一次是创建核心线程,其他2次都是非核心线程,为什么呢?

我的理解:第一次毋庸置疑;关键是第二次,我一开始很迷惑,第二次是在工作线程为0时调用的,为什么不传true是判断核心线程;我们现在假设此时是创建核心线程,即false改为true;那么addWorker方法中wc >= (core ? corePoolSize : maximumPoolSize)这个地方会去判断当前工作线程是否大于核心线程,在高并发的情况下,会存在其他线程将工作线程的数量创建的大于核心线程数,导致返回false,并且不会创建新线程,虽然有工作线程的存在,但是会导致原本可以及时处理的任务,要去排队执行。
第三次显而易见必须是false,因为经过前面几次后,剩下的结果,要么是线程池挂了,要是是队列满了,所以一定是创建非核心线程;

addWorker

private boolean addWorker(Runnable firstTask, boolean core) {
        retry:
        for (;;) {
            int c = ctl.get();
            int rs = runStateOf(c);

            // Check if queue empty only if necessary.
            //判断线程池的是否可以接收新任务
            if (rs >= SHUTDOWN &&
                ! (rs == SHUTDOWN &&
                   firstTask == null &&
                   ! workQueue.isEmpty()))
                return false;

            for (;;) {
            	//获取工作线程的数量
                int wc = workerCountOf(c);
                //判断工作线程的数量是否大于等于线程池的上限或者核心或者最大线程数
                if (wc >= CAPACITY ||
                    wc >= (core ? corePoolSize : maximumPoolSize))
                    return false;
                //使用cas增加工作线程数
                if (compareAndIncrementWorkerCount(c))
                    break retry;
                c = ctl.get();  // Re-read ctl
                //如果添加失败,并且线程池状态发生了改变,重来一遍
                if (runStateOf(c) != rs)
                    continue retry;
                // else CAS failed due to workerCount change; retry inner loop
            }
        }
		//上面的逻辑是考虑是否能够添加线程,如果可以使用cas来增加工作线程数量
		//下面正式启动线程
        boolean workerStarted = false;
        boolean workerAdded = false;
        Worker w = null;
        try {
        	//新建worker
            w = new Worker(firstTask);
            // 获取当前线程
            final Thread t = w.thread;
            if (t != null) {
            	//获取重入锁
                final ReentrantLock mainLock = this.mainLock;
                //锁住
                mainLock.lock();
                try {
                    // Recheck while holding lock.
                    // Back out on ThreadFactory failure or if
                    // shut down before lock acquired.
                    int rs = runStateOf(ctl.get());
					// rs < SHUTDOWN  -- 状态即为:RUNNING
					//rs == SHUTDOWN && firstTask == null 
                    if (rs < SHUTDOWN ||
                        (rs == SHUTDOWN && firstTask == null)) {
                        //如果线程已经启动,抛出异常
                        if (t.isAlive()) // precheck that t is startable
                            throw new IllegalThreadStateException();
                        //workers是一个HashSet,必须在锁住的情况下,操作
                        workers.add(w);
                        int s = workers.size();
                        //设置largestPoolSize ,标记workerAdded 
                        if (s > largestPoolSize)
                            largestPoolSize = s;
                        workerAdded = true;
                    }
                } finally {
                    mainLock.unlock();
                }
                //如果添加成功,启动线程
                if (workerAdded) {
                    t.start();
                    workerStarted = true;
                }
            }
        } finally {
        	//启动线程失败,回滚
            if (! workerStarted)
                addWorkerFailed(w);
        }
        return workerStarted;
    }

该方法的思路:
1、考虑是否能够添加线程,如果可以使用cas来增加工作线程数量
2、正式启动线程

Worker 本身也是一个任务因为其实现了Runnable,并且其继承了AbstractQueuedSynchronizer,所以也具备锁的效果。

而我们提交的任务,是在Worker方法中的run方法中调用的。


一开始我还有疑问,为什么启动Worker属性中的Thread,就能运行Worker
其构造函数是这样的:

Worker(Runnable firstTask) {
            setState(-1); // inhibit interrupts until runWorker
            this.firstTask = firstTask;
            this.thread = getThreadFactory().newThread(this);
        }

可以看出这句this.thread = getThreadFactory().newThread(this);就把当前的Worker对象作为任务传给了新建的进程。这样启动进程时,它也就启动了。

总结

①当工作线程小于核心线程时,会创建核心线程。
②如果线程池中的线程数量大于等于corePoolSize,但队列workQueue未满,则将新添加的任务放到workQueue中,按照FIFO的原则依次等待执行(线程池中有线程空闲出来后依次将队列中的任务交付给空闲的线程执行);
③当线程池处于非RUNNING状态时,会尝试将刚刚加入到队列的任务移除掉,如果移除失败,并且工作线程为0情况下,就会尝试创建一个非核心线程,来消费队列。
④如果线程池中的线程数量大于等于corePoolSize,且队列workQueue已满,但线程池中的线程数量小于maximumPoolSize,则会创建新的线程来处理被添加的任务;
⑤如果线程池中的线程数量等于了maximumPoolSize,就用RejectedExecutionHandler来做拒绝处理

当有新的任务要处理时,先看线程池中的线程数量是否大于corePoolSize,再看缓冲队列workQueue是否满,最后看线程池中的线程数量是否大于maximumPoolSize
另外,当线程池中的线程数量大于corePoolSize时,如果里面有线程的空闲时间超过了keepAliveTime,就将其移除线程池

=-=-=-=-=-=-=-=2020年08月31日-=-=-=-=-=-=-=-=-=-=start-=-=-=
今天在jdk11的代码上看addWorker方法,发现有个地方,源码上有多变化;
在jdk8中addWorker()方法是:

...省略...
for (;;) {
        	//获取工作线程的数量
            int wc = workerCountOf(c);
            //判断工作线程的数量是否大于等于线程池的上限或者核心或者最大线程数
            if (wc >= CAPACITY ||
                wc >= (core ? corePoolSize : maximumPoolSize))
...省略...

但是到了jdk11中,其优化了下,变成了如下:

for (;;) {
          if (workerCountOf(c)
              >= ((core ? corePoolSize : maximumPoolSize) & COUNT_MASK))

本质上含义是一样的,都是判断工作线程的数量是否大于等于线程池的上限或者核心或者最大线程数
为什么可以改写成这样(core ? corePoolSize : maximumPoolSize) & COUNT_MASK)呢?

COUNT_MASK就是jdk8中的CAPACITY。

这是因为COUNT_MASK表示的是000 11111 11111111 11111111 1111111表示是线程池最大能表示的数字,即线程池的上限;
可以看到其高位三个数都是0,因为高位表示的是runstate,即线程池运行状态;都为0,也就是不需要这三个数;那么这个COUNT_MASK和任何数字做&运算;都会等于该数字本身,并且做&运算,保证了这个数字不会超过COUNT_MASK本身,即线程池的上限;
=-=-=-=-=-=-=-=2020年08月31日-=-=-=-=-=-=-=-=-=-=end-=-=-=

参考地址:

Java线程池ThreadPoolExecutor使用和分析
线程池 execute() 的工作逻辑
ThreadPoolExecutor源码分析(一):重要成员变量

Java线程池ThreadPoolExecutorJava提供的一个用于管理和复用线程的工具类。它可以帮助我们更有效地管理线程资源,提高程序的性能和可维护性。 下面是一个简单的使用ThreadPoolExecutor的示例代码: ```java import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; public class ThreadPoolExample { public static void main(String[] args) { // 创建一个线程池,其包含5个线程 ExecutorService executor = Executors.newFixedThreadPool(5); // 提交任务给线程池执行 for (int i = 0; i < 10; i++) { Runnable worker = new WorkerThread("Task " + i); executor.execute(worker); } // 关闭线程池 executor.shutdown(); while (!executor.isTerminated()) { // 等待所有任务完成 } System.out.println("所有任务已完成"); } } class WorkerThread implements Runnable { private String taskName; public WorkerThread(String taskName) { this.taskName = taskName; } @Override public void run() { System.out.println(Thread.currentThread().getName() + " 开始执行任务:" + taskName); try { // 模拟任务执行时间 Thread.sleep(2000); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(Thread.currentThread().getName() + " 完成任务:" + taskName); } } ``` 上述代码,首先通过`Executors.newFixedThreadPool(5)`创建了一个包含5个线程的线程池。然后使用`executor.execute(worker)`提交任务给线程池执行,其`worker`是实现了`Runnable`接口的任务对象。任务会被线程池的线程异步执行。 最后,通过`executor.shutdown()`关闭线程池,并使用`executor.isTerminated()`等待所有任务完成。完成后输出"所有任务已完成"。
评论 9
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

山鬼谣me

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值