线程池的执行原理解析

149 阅读8分钟

在Java中,Executor接口是Java并发框架中的一个核心接口,用于定义一种将任务提交给线程池或其他执行机制的方法,而无需显式地管理线程的生命周期。Executor接口的主要作用是提供一种解耦的方式,使得任务的提交和任务的执行分离,从而简化并发编程的复杂性。下图是Executor接口关键的继承实现图 image-20250126221609962

以下是Executor接口的一些关键点及其作用:

  1. 任务提交与执行的分离

    • 通过Executor接口,你可以将任务(通常是实现了Runnable接口的对象)提交给执行器(Executor),而不需要关心这些任务是如何被线程执行的
  2. 简化并发编程

    • Executor接口使得开发者无需手动创建和管理线程,从而简化了并发编程。你可以使用现有的线程池(如Executors工厂类提供的线程池)来执行你的任务,这样可以更有效地利用系统资源。
  3. 灵活性

    • Executor接口的实现可以非常灵活,包括单线程执行器、固定线程池、缓存线程池、调度线程池等。这使得你可以根据具体的应用场景选择最合适的执行机制。
  4. 异常处理

    • Executor接口允许你定义任务的异常处理策略。例如,你可以通过自定义的ThreadFactoryUncaughtExceptionHandler来管理线程创建和未捕获异常的处理。
  5. 关闭和生命周期管理

    • Executor接口通常与ExecutorService接口一起使用,后者提供了更丰富的生命周期管理功能,如shutdown()awaitTermination()方法,用于优雅地关闭线程池和等待所有任务完成。

说的更直白一些,Executor.execute(Runnable command)是给程序员用的,从语义上可以看出是让程序员用执行器执行任务,底层的执行过程被屏蔽掉。而底层的执行逻辑又可以分成两部分

  1. 以ThreadPoolExecutor为例,在接收到任务之后,会根据情况创建线程或将任务添加至任务队列
  2. 上个步骤创建出的线程会不断地轮询任务队列。

针对上面两个阶段,通过下面的两节进行详细讲解。

1 提交任务给线程池

以默认实现ThreadPoolExecutor为例,创建线程池的构造函数如下

public ThreadPoolExecutor(int corePoolSize,
                              int maximumPoolSize,
                              long keepAliveTime,
                              TimeUnit unit,
                              BlockingQueue<Runnable> workQueue,
                              ThreadFactory threadFactory,
                              RejectedExecutionHandler handler)

各个参数的含义:

  • corePoolSize,核心线程数量;
  • maximumPoolSize,最大线程数量;
  • keepAliveTime,超过核心线程数量线程的在空闲后的存活时间;
  • unit,存活时间的时间单位;
  • workQueue,超过核心线程后任务存放的阻塞队列;
  • threadFactory,常用定义线程名字的线程工厂,可以使用默认工厂;
  • handler,是阻塞队列已满,并且线程数达到maximumPoolSize的时候的处理策略,其中包括了抛出异常(AbortPolicy),谁请求谁调用(CallerRunsPolicy),丢弃线程池中的一个任务来执行现在的任务(DiscardOldesPolicy),直接丢弃掉(DiscardPolicy)默认使用抛异常策略。

下面是线程池的状态值:

    private final AtomicInteger ctl = new AtomicInteger(ctlOf(RUNNING, 0));
    private static final int COUNT_BITS = Integer.SIZE - 3;
    private static final int CAPACITY   = (1 << COUNT_BITS) - 1;
 
    // runState is stored in the high-order bits
    private static final int RUNNING    = -1 << COUNT_BITS;
    private static final int SHUTDOWN   =  0 << COUNT_BITS;
    private static final int STOP       =  1 << COUNT_BITS;
    private static final int TIDYING    =  2 << COUNT_BITS;
    private static final int TERMINATED =  3 << COUNT_BITS;

ctl字段的高3位用于维护线程池运行状态,低29位维护线程池中线程数量。高三位中,

RUNNING111,SHUTDOWN为000,STOP为001,TIDYING为010,TERMINATED为100

线程池执行的过程主要有以下特点:

1、如果线程池中的线程数量少于corePoolSize,就创建新的线程来执行新添加的任务 2、如果线程池中的线程数量大于等于corePoolSize,但队列workQueue未满,则将新添加的任务放到workQueue中 3、如果线程池中的线程数量大于等于corePoolSize,且队列workQueue已满,但线程池中的线程数量小于maximumPoolSize,则会创建新的线程来处理被添加的任务 4、如果线程池中的线程数量等于了maximumPoolSize,就用RejectedExecutionHandler来执行拒绝策略

下面跟踪源码,看看具体的实现过程: 首先是将任务提交给线程池

public void execute(Runnable command) {
        if (command == null)
            throw new NullPointerException();
        //上面对属性的介绍中,我们知道,ctl就是线程池状态(高三位)和线程数量(低29位)的组合
        int c = ctl.get();
        //workerCountOf函数就是获取线程的数量
        //如果已有线程数量小于核心线程数,就调addWorker方法添加一个worker来执行任务
        if (workerCountOf(c) < corePoolSize) {
            if (addWorker(command, true))
                return;
            c = ctl.get();
        }
        //代码如果运行到这里,那么就说明要么线程数大于等于核心线程数,要么addWorker失败
        //再次判断线程池是不是运行状态,如果是运行状态,则把当前任务添加到workQueue中
        if (isRunning(c) && workQueue.offer(command)) {
        //如果添加任务成功,则再次获取当前的线程池状态,判断线程池是否处于运行状态,如果线程池已经关闭,那么就移除该任务,并走拒绝策略;
            int recheck = ctl.get();
            if (! isRunning(recheck) && remove(command))
                reject(command);
            else if (workerCountOf(recheck) == 0)
        //当线程池处于运行状态但当前没有活跃线程,需要创建新线程。
                addWorker(null, false);
        }
        else if (!addWorker(command, false))
            reject(command);
    }

下面是创建线程的逻辑

private boolean addWorker(Runnable firstTask, boolean core) {
//入参中,firstTask是任务,第二个参数:core若是true,代表创建的是核心线程,线程池运行期间根据开关配置,可以不用消亡;若是false,则代表创建的是非核心线程,一定的时间内没有任务执行,一定就会失效。这些逻辑判断在后面Woker的代码中会介绍
这里说下入参中任务的名字是firstTask,因为在创建线程的时候,如果同时指定了 这个线程起来以后需要执行的第一个任务,那么第一个任务就是存放在这里的(线程可不止执行这一个任务) 。当然了,也可以为 null,这样线程起来了,自己到任务队列(BlockingQueue)中取任务(getTask 方法)就行了。
        retry:
        for (;;) {
            int c = ctl.get();
            int rs = runStateOf(c);
 
            // rs >= SHUTDOWN代表的是非running状态
            if (rs >= SHUTDOWN &&
                ! (rs == SHUTDOWN &&
                   firstTask == null &&
                   ! workQueue.isEmpty()))
                return false;
 
            for (;;) {
                int wc = workerCountOf(c);
            //判断当前线程数数量,core=true:大于等于最大核心线程数,或core=false:大于等于最大线程数时,是不允许新开线程的
                if (wc >= CAPACITY ||
                    wc >= (core ? corePoolSize : maximumPoolSize))
                    return false;
                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
            }
        }
 
        boolean workerStarted = false;
        boolean workerAdded = false;
        Worker w = null;
        try {
        //创建worker,把任务添加进去,worker内会通过线程工厂创建出一个thread
            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());
                
                    if (rs < SHUTDOWN ||
                        (rs == SHUTDOWN && firstTask == null)) {
                        if (t.isAlive()) // precheck that t is startable
                            throw new IllegalThreadStateException();
                        workers.add(w);
                        int s = workers.size();
              //因为 workers 是不断增加减少的,通过这个值可以知道线程池的大小曾经达到的最大值
                        if (s > largestPoolSize)
                            largestPoolSize = s;
                        workerAdded = true;
                    }
                } finally {
                    mainLock.unlock();
                }
              //添加成功的话,启动线程
                if (workerAdded) {
                    t.start();
                    workerStarted = true;
                }
            }
        } finally {
            if (! workerStarted)
                addWorkerFailed(w);
        }
        return workerStarted;
    }

2 线程轮询执行任务过程

当worker启动的时候,就进入了Worker的run方法了。

        public void run() {
            runWorker(this);
        }
final void runWorker(Worker w) {
        Thread wt = Thread.currentThread();
//这里会从worker中获取任务,就是在addWorker方法中添加的任务,若在addWorker没有添加任务,那么这里获取到的就是null
        Runnable task = w.firstTask;
        w.firstTask = null;
        w.unlock(); // allow interrupts
        boolean completedAbruptly = true;
        try {
//这里先看Worker本身的task是否为空,不为空就处理该任务,如果为空,则调getTask方法获取任务来执行
            while (task != null || (task = getTask()) != null) {
                w.lock();
                // If pool is stopping, ensure thread is interrupted;
                // if not, ensure thread is not interrupted.  This
                // requires a recheck in second case to deal with
                // shutdownNow race while clearing interrupt
                if ((runStateAtLeast(ctl.get(), STOP) ||
                     (Thread.interrupted() &&
                      runStateAtLeast(ctl.get(), STOP))) &&
                    !wt.isInterrupted())
                    wt.interrupt();
                try {
                    beforeExecute(wt, task);
                    Throwable thrown = null;
                    try {
//由于我们添加的任务都是事先runnable的,这里就是调用添加的任务的run方法
                        task.run();
                    } catch (RuntimeException x) {
                        thrown = x; throw x;
                    } catch (Error x) {
                        thrown = x; throw x;
                    } catch (Throwable x) {
                        thrown = x; throw new Error(x);
                    } finally {
                        afterExecute(task, thrown);
                    }
                } finally {
                    task = null;
                    w.completedTasks++;
                    w.unlock();
                }
            }
            completedAbruptly = false;
        } finally {
            processWorkerExit(w, completedAbruptly);
        }
    }

从上面的方法中可以看到,线程池中的线程就是通过while循环不断通过getTask方法获取任务并执行任务的。那么getTask方法就很关键了,因为getTask是否能返回以及是返回的任务是否为空,决定了while循环是否会退出。下面看下getTask是怎么实现的:

//该方法主要就是获取一个任务,前面我们讲了核心线程不会消亡(allowCoreThreadTimeOut不为空的情况下),会一直查询queue中是否有任务并执行,但是非核心线程在一定的时间内获取不到任务就消亡了,具体逻辑就在这里
private Runnable getTask() {
        boolean timedOut = false; // Did the last poll() time out?
 
        for (;;) {
            int c = ctl.get();
            int rs = runStateOf(c);
 
            // Check if queue empty only if necessary.
            if (rs >= SHUTDOWN && (rs >= STOP || workQueue.isEmpty())) {
                decrementWorkerCount();
                return null;
            }
 
            int wc = workerCountOf(c);
 
            // 若allowCoreThreadTimeOut为true,或线程数量大于核心线程时,timed为true
            boolean timed = allowCoreThreadTimeOut || wc > corePoolSize;
 
            if ((wc > maximumPoolSize || (timed && timedOut))
                && (wc > 1 || workQueue.isEmpty())) {
                if (compareAndDecrementWorkerCount(c))
                    return null;
                continue;
            }
 
            try {
        //若允许线程消亡,则调poll方法获取任务,等待一定的时间,没有任务就直接返回null
        //若不允许消亡,则调take方法获取任务,没有任务的话,会一直阻塞在这里
                Runnable r = timed ?
                    workQueue.poll(keepAliveTime, TimeUnit.NANOSECONDS) :
                    workQueue.take();
                if (r != null)
                    return r;
                timedOut = true;
            } catch (InterruptedException retry) {
                timedOut = false;
            }
        }
    }