java 线程池的原理

260 阅读11分钟

一、什么是线程池

程序中常常使用多线程的方式充分利用CPU的多核特性,但是线程过多会带来额外的开销,比如创建、销毁线程的开销,调度线程的开销等,给系统带来了额外的问题。线程池通过维护一组线程池,给线程分配任务并执行,从而避免了线程的创建、销毁开销,和线程无限制创建带来的调度和内存占用问题。

线程池的优势:

  1. 线程池维护了一组线程,这些线程可通过拉取任务的方式执行任务,从而达到了复用的效果;

  2. 任务达到时,线程池将任务分配给对应线程执行,从而提高任务执行的效率;

  3. 提高了对线程的管理,​线程池中不会无限制创建线程。

对于线程池,我们主要关心以下两点:

1、当任务到来时,任务是如果被执行的,是直接创建线程并执行,还是先缓存在任务队列中执行;线程是如何起到复用的效果的。

2、线程池生命周期是什么,线程shutdown()和shutdownNow()二者的区别是什么。

java中通过ThreadPoolExcecutor类​提供线程池能力,本文以ThreadPoolExecutor作为入口分析java线程池的原理,并对以上两个问题进行解答。

二、线程池构造方法

ThreadPoolExecutor的构造方法:

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

勘误:当线程池数量达到coresize之后,将任务丢到队列中,如果队列满,而且当前线程池数量 < maximumPoolSize,则创建线程执行任务,否则执行拒绝策略。

三、 任务执行机制

3.1 任务提交

ThreadPoolExecutor父类AbstractExecutorService定义了submit(Runnable task)的实现方式。

public <T> Future<T> submit(Callable<T> task) {
        if (task == null) throw new NullPointerException();
        //将task封装成RunnableFuture,由execute(ftask)执行。
        RunnableFuture<T> ftask = newTaskFor(task);
        execute(ftask);
        return ftask;
    }
​

void execute(Runnable command)由ThreadPoolExecutor实现:

public void execute(Runnable command) {
        if (command == null)
            throw new NullPointerException();
        int c = ctl.get();
        //如果线程池线程数量小于corePoolSize,新建Worker(新建线程),执行任务
        if (workerCountOf(c) < corePoolSize) {
            if (addWorker(command, true))
                return;
            c = ctl.get();
        }
        //否则,如果当前线程池的状态是RUNNING,则尝试在阻塞队列中加入任务
        if (isRunning(c) && workQueue.offer(command)) {
            int recheck = ctl.get();
            //如果线程池的状态不是RUNNING,从阻塞队列中移除该任务
            if (! isRunning(recheck) && remove(command))
                //执行拒绝策略
                reject(command);
            //如果此时线程池中线程数量为0,则新建worker,从阻塞队列中捞任务并执行。
            else if (workerCountOf(recheck) == 0)
                addWorker(null, false);
        }
        //如果线程池数量>corePoolSize而且阻塞队列满 || 线程池的状态非RUNNING,不再接受新提交任务
        //执行拒绝策略。
        else if (!addWorker(command, false))
            reject(command);
    }
​

流程图如下:

3.2 任务缓冲

当corePoolSize < 线程数量 < maximumPoolSize时,新提交的任务会被缓存到阻塞队列中,线程池中的线程会从阻塞队列中获取任务,当获取不到任务的时候,会阻塞线程。

private final BlockingQueue<Runnable> workQueue;

常用的阻塞队列实现:

3.3 添加Worker

ThreadPoolExecutor通过内部类Worker控制线程的执行,Worker实现了AbstractQueuedSynchronizer和Runnable,Worker是独占、不可重入的锁。

private final class Worker
        extends AbstractQueuedSynchronizer
        implements Runnable
    {
        //线程
        final Thread thread;
        //初始化时的task
        Runnable firstTask;
        //执行成功的任务数量
        volatile long completedTasks;
        Worker(Runnable firstTask) {
            setState(-1); // inhibit interrupts until runWorker
            this.firstTask = firstTask;
            this.thread = getThreadFactory().newThread(this);
        }
        //独占不可重入的锁获取操作。
        protected boolean tryAcquire(int unused) {
            if (compareAndSetState(0, 1)) {
                setExclusiveOwnerThread(Thread.currentThread());
                return true;
            }
            return false;
        }
​
        protected boolean tryRelease(int unused) {
            setExclusiveOwnerThread(null);
            setState(0);
            return true;
        }
}

下面介绍addWorker的逻辑,addWorker()完成的操作主要是创建线程执行任务,如果firstTask为null,则从workQueue中捞取任务执行。

private boolean addWorker(Runnable firstTask, boolean core) {
        retry:
        for (;;) {
            int c = ctl.get();
            int rs = runStateOf(c);
​
            // 如果线程池状态为SHUTDOWN、STOP、TYDING、TERMINATED,不允许新提交任务,直接返回
            // 如果状态为SHUTDOWN,如果阻塞队列不为空,而且firstTask为null,表示可以新建线程执行
            // 阻塞队列中的任务;或者线程池的状态为RUNNING
      
            if (rs >= SHUTDOWN &&
                ! (rs == SHUTDOWN &&
                   firstTask == null &&
                   ! workQueue.isEmpty()))
                return false;
​
            for (;;) {
                int wc = workerCountOf(c);
                //线程池的数量不能超过阈值,该阈值由调动addWorker()的时机决定。
                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
            }
        }
​        //此时线程池的数量已经加1
        boolean workerStarted = false;
        boolean workerAdded = false;
        Worker w = null;
        try {
            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());
​                    //如果线程池状态为RUNNING,或者为SHUTDOWN && firstTask为null,可以新建worker
                    //执行任务。
                    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();
                        if (s > largestPoolSize)
                            largestPoolSize = s;
                        workerAdded = true;
                    }
                } finally {
                    mainLock.unlock();
                }
                //如果workder被成功添加,调用线程的start(),执行任务。
                if (workerAdded) {
                    t.start();
                    workerStarted = true;
                }
            }
        } finally {
            if (! workerStarted)
                addWorkerFailed(w);
        }
        return workerStarted;
    }​

流程图如下:

执行流程:

  1. 如果线程池状态 =RUNNING, 线程池可以创建worker执行firstTask 或者线程池状态=SHUTDOWN,&& firstTask=null && workQueue isNotEmpty,表示可以从任务队列中获取任务执行;否则,不能创建worker;
  2. 当前线程池中的线程数量是否超过阈值,如果超过阈值,则不允许创建worker执行任务:阈值由入参core决定,core为true,表示当前线程数量<corePoolSize,阈值为corePoolSize;否则,表示当前线程数量>=corePoolSize,阈值为maximumPoolSize;
  3. 获取线程池的全局锁,创建worker,加入到线程池中,启动线程,执行任务;如果线程启动失败,则调用addWorkerFailed()完成回滚、尝试关闭线程池等操作。

3.4 Worker执行

线程运行会调用Worker.run()方法:

public void run() {
            runWorker(this);
        }​

runWorker(Worker woker):

final void runWorker(Worker w) {
        //获取到当前线程
        Thread wt = Thread.currentThread();
        //拿到Worker创建时的task
        Runnable task = w.firstTask;
        w.firstTask = null;
        //worker创建是的同步状态state=-1,调用此方法确保state=0,state>=0说明
        //worker已经进入了runWorker()开始运行了,只有进入了runWorker()时才有被中断的意义
        //interruptIfStarted()只中断state>=0的worker;
        w.unlock(); // allow interrupts
        boolean completedAbruptly = true;
        try {
            //如果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
                //确保线程池的状态在STOP之前,线程没有中断置位;确保线程池的状态>=STOP,线程中断
                if ((runStateAtLeast(ctl.get(), STOP) ||
                     (Thread.interrupted() &&
                      runStateAtLeast(ctl.get(), STOP))) &&
                    !wt.isInterrupted())
                    wt.interrupt();
                try {
                    //执行任务之前的前置操作,由子类实现
                    beforeExecute(wt, task);
                    Throwable thrown = null;
                    try {
                       //执行任务
                        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;
                    //worker完成执行的任务数+1
                    w.completedTasks++;
                    //放弃独占锁,表示线程此时可以被中断
                    w.unlock();
                }
            }
            //从以上代码中看出,只有当task.run()执行时抛异常,也就是外部业务代码抛了异常,这行代码
            //才不会执行,也就是说completedAbruptly=true表示是由于业务代码执行异常。
            completedAbruptly = false;
        } finally {
            //如果是因为用户的业务代码抛出异常,线程数量-1,剔除该线程;
            //否则,回收当前线程,并判断是否需要新建替代线程。
            processWorkerExit(w, completedAbruptly);
        }
    }​

线程通过runWorker()中循环调用getTask(),起到了线程复用的功能。

3.5 任务获取

ThreadPoolExecutor.getTask():

private Runnable getTask() {
        boolean timedOut = false; // Did the last poll() time out?
​
        for (;;) {
            int c = ctl.get();
            int rs = runStateOf(c);
​
            // 当线程池状态>=SHUTDOWN时,只有状态为SHUTDOWN而且任务队列不为空,才允许执行任务            //也才能从任务队列中获取任务。
            if (rs >= SHUTDOWN && (rs >= STOP || workQueue.isEmpty())) {
                //线程池线程数量-1
                decrementWorkerCount();
                return null;
            }
​
            int wc = workerCountOf(c);
​
            // 线程是否允许因为超时被回收;1、allowCoreThreadTimeOut=true说明核心线程超时可以被回收;
            // 2、wc > corePoolSize,当前线程数量>核心线程数,也能回收。
            boolean timed = allowCoreThreadTimeOut || wc > corePoolSize;
​             //如果线程数量超过maximumPoolSize || 超时,退出任务的获取,并减少线程数量                
            if ((wc > maximumPoolSize || (timed && timedOut))
                && (wc > 1 || workQueue.isEmpty())) {
                if (compareAndDecrementWorkerCount(c))
                    return null;
                continue;
            }
​
            try {
                //从workQueue中获取任务,
                Runnable r = timed ?
                    workQueue.poll(keepAliveTime, TimeUnit.NANOSECONDS) :
                    workQueue.take();
                if (r != null)
                    return r;
                //因为超时未获取到任务,r=null,timeOut=true用于标识超时
                timedOut = true;
            } catch (InterruptedException retry) {
                timedOut = false;
            }
        }
    }​

流程图如下:

3.6 线程退出

processWorkerExit()方法主要是处理线程退出任务执行后,执行的逻辑。

private void processWorkerExit(Worker w, boolean completedAbruptly) {
        //如果是执行过程中,因为业务代码抛异常,线程数量-1.
        if (completedAbruptly) // 
            decrementWorkerCount();
​
        final ReentrantLock mainLock = this.mainLock;
        //获取线程池的锁
        mainLock.lock();
        try {
            completedTaskCount += w.completedTasks;
            //移除woker
            workers.remove(w);
        } finally {
            mainLock.unlock();
        }
​        //尝试terminate线程池。
        tryTerminate();
​
        int c = ctl.get();
        //如果线程池的状态为RUNNING或者SHUTDOWN
        if (runStateLessThan(c, STOP)) {
            //如果是因为用户导致的退出,不用新建线程。
            if (!completedAbruptly) {
                int min = allowCoreThreadTimeOut ? 0 : corePoolSize;
                if (min == 0 && ! workQueue.isEmpty())
                    min = 1;
                //判断当前线程数量是否满足最低要求,如果核心线程允许超时,则表示线程池最少应该有1个线程;
                // 否则,corePoolSize是线程池的最低线程数
                if (workerCountOf(c) >= min)
                    return; // replacement not needed
            }
            //添加worker,task=null,从任务队列中获取任务执行
            addWorker(null, false);
        }
    }​
​
​

completedAbruptly:true表示因为用户代码执行的异常导致的退出,false表示线程是因为获取阻塞队列中的任务失败导致的退出。

流程图如下:

当线程池中的线程执行到processWorkerExit()时,说明线程要么是因为用户代码抛出异常,或者线程从workQueue里面获取task为null,导致线程退出。对于第一种情况,线程因为用户代码的问题退出,不需要再向线程池中添加替代线程;第二种情况,则要根据当前线程池需要的最低线程数,判断是否需要新建线程。通过tryTerminate()的执行,尝试去关闭线程池。

四、 线程池的生命周期

    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;
​
    // 线程池的几种状态
    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;
​
    // 返回线程池的状态
    private static int runStateOf(int c)     { return c & ~CAPACITY; }
    // 返回线程池的线程数量
    private static int workerCountOf(int c)  { return c & CAPACITY; }
    private static int ctlOf(int rs, int wc) { return rs | wc; }​

ThreadPoolExecutor使用原子类AtomicInteger表示线程池的生命周期,其中高3位表示线程池的状态,对应的含义如下:

五种状态的流转关系:

ThreadPoolExecutor提供了两种关闭线程池的方法:

注:shutdownNow()会尝试停止所有正在执行的线程,但是不会保证线程立马停止,而是调用Thread.interrupt(),只有线程调用了wait()、sleep()、获取响应中断的锁等操作时,才会通过InterruptException的方式响应,否则线程会将任务执行完毕后才停止。

4.1 shutdown()原理

public void shutdown() {
        //获取线程池的锁
        final ReentrantLock mainLock = this.mainLock;
        mainLock.lock();
        try {
            //检查调用者是否有权限shutdown线程池
            checkShutdownAccess();
            //将线程池的状态设置为SHUTDOWN
            advanceRunState(SHUTDOWN);
            //中断空闲的线程,使这些线程
            interruptIdleWorkers();
            onShutdown(); // hook for ScheduledThreadPoolExecutor
        } finally {
            mainLock.unlock();
        }
        //尝试关闭线程池
        tryTerminate();
    }​

private void interruptIdleWorkers() {
        interruptIdleWorkers(false);
​    }​

private void interruptIdleWorkers(boolean onlyOne) {
        final ReentrantLock mainLock = this.mainLock;
        mainLock.lock();
        try {
            for (Worker w : workers) {
                Thread t = w.thread;
                //如果线程没有被中断 && 线程没有正在执行任务。
                if (!t.isInterrupted() && w.tryLock()) {
                    try {
                        //中断线程
                        t.interrupt();
                    } catch (SecurityException ignore) {
                    } finally {
                        w.unlock();
                    }
                }
                if (onlyOne)
                    break;
            }
        } finally {
            mainLock.unlock();
        }
    }​

在3.3节中,runWoker()方法在执行时,如果线程获取到了任务,会调用w.lock(),用于标识该线程正在执行任务,这样如果w.tryLock()返回false则说明线程处于空闲中,可以中断。调用interruptIdleWorkers()方法的意义是让线程从在getTask() 中pool/task的阻塞中退出去,重新开始循环获取任务,让该线程能感知到线程池的改变,如果此时线程池状态为SHUTDOWN,而且workQueue为empty,则退出任务的获取,执行线程的退出逻辑。

存在一种情况,当执行完interruptIdleWorkers()后,线程池中可能存在正在执行任务的线程,当这些线程执行任务后,继续循环调用getTask(),如果workQueue.isEmpty,线程会一直阻塞在queue.take()中,导致线程无法退出。对于这部分线程的退出,java设计者采用的方法是调用tryTerminate()。

tryTerminate()方法会尝试将线程池的状态设置为TERMINATED。

  1. 线程如果处于RUNNING ,或者线程状态为SHUTDOWN 而且任务队列不为空,说明线程池还有任务,则不能讲线程池的状态设置为TERMINATED,返回;或者线程池的状态>=TIDYING,说明线程池正在被其他调用者terminated中,返回;

  2. 如果线程池中的线程数量> 0,则尝试中断一个空闲的线程;

  3. 获取线程池的锁,尝试将线程池的状态设置为TYDING,如果成功,则最终将线程池的状态设置为TERMINATED。

    final void tryTerminate() { for (;;) { int c = ctl.get(); //如果线程池状态为RUNNING,或者线程池状态>=TYDING,或者线程池状态为 //SHUTDOWN,但是任务队列不为空,不能关闭线程池。 if (isRunning(c) || runStateAtLeast(c, TIDYING) || (runStateOf(c) == SHUTDOWN && ! workQueue.isEmpty())) return; //执行到此处,说明线程池状态=SHUTDOWN && 任务队列中空;或者线程池状态=STOP if (workerCountOf(c) != 0) { // Eligible to terminate //中断一个处于getTask()阻塞中的线程, interruptIdleWorkers(ONLY_ONE); return; } ​ final ReentrantLock mainLock = this.mainLock; mainLock.lock(); try { if (ctl.compareAndSet(c, ctlOf(TIDYING, 0))) { try { terminated(); } finally { ctl.set(ctlOf(TERMINATED, 0)); termination.signalAll(); } return; } } finally { mainLock.unlock(); } // else retry on failed CAS } }​

这个方法中比较晦涩难懂的是为什么需要调用interruptIdleWorkers(ONLY_ONE);

答案:如果方法执行到此处,说明线程池的状态是STOP || (SHUTDOWN && workQueue isEmpty),而且此时线程数量>1,这就说明线程池本身没有任务可执行了,但是线程被阻塞在getTask() queue.take()中,通过调用interruptIdleWorkers(true),会将一个阻塞的线程唤醒退出getTask(),然后调用runWorker()中的processWorkerExit(w, completedAbruptly)方法,执行线程的退出逻辑。在processWorkerExit()中可能会通过调用addWorker(null, false)新创建一个worker,但是这个worker会在getTask()中因为线程池的状态以及workQueue isEmpty,最终执行线程的退出逻辑。processWorkerExit()的执行中,还会调用tryTerminate(),继续之前的步骤,最终线程池中阻塞在getTask()的所有线程都会被唤醒,退出getTask(),最终被回收。

另一个问题,为什么Worker要设计为不可重入?

Worker可以通过调用setCoreSize(size)方法动态调整线程池的核心线程数量:

public void setCorePoolSize(int corePoolSize) {
        if (corePoolSize < 0)
            throw new IllegalArgumentException();
        int delta = corePoolSize - this.corePoolSize;
        this.corePoolSize = corePoolSize;
        if (workerCountOf(ctl.get()) > corePoolSize)
            interruptIdleWorkers();
        else if (delta > 0) {
            // We don't really know how many new threads are "needed".
            // As a heuristic, prestart enough new workers (up to new
            // core size) to handle the current number of tasks in
            // queue, but stop if queue becomes empty while doing so.
            int k = Math.min(delta, workQueue.size());
            while (k-- > 0 && addWorker(null, true)) {
                if (workQueue.isEmpty())
                    break;
            }
        }
    }​

该方法内部调用interruptIdleWorkers(),通过tryLock()的方式获取空闲的线程,然后中断这些线程。如果AQS可重入,那么worker线程被自身中断,导致worker线程被错误回收,这显然是不被允许的。为了解决这种边界问题,Worker被设计成不支持线程重入的AQS。

4.2 shutdownNow()逻辑

public List<Runnable> shutdownNow() {
        List<Runnable> tasks;
        final ReentrantLock mainLock = this.mainLock;
        mainLock.lock();
        try {
            checkShutdownAccess();
            //设置线程池的状态为STOP
            advanceRunState(STOP);
            //中断所有线程
            interruptWorkers();
            //获取workQueue中的所有任务,退出队列
            tasks = drainQueue();
        } finally {
            mainLock.unlock();
        }
        //尝试关闭线程池
        tryTerminate();
        return tasks;
    }​

五、线程池核心参数的设置

假设一个任务的执行时间是t,每秒的任务达到数是100-1000,80%的时间中每秒到达的任务数量在200以内。

5.1 coreSize:

当线程数量小于coreSize时,当一个任务达到时,线程池会创建线程立即执行该任务,核心线程应该能处理80%的流量情况。计算方法:

coreSize=200/(1/0.1)=20,意思是1个线程1s可以处理10个任务,那么200个任务,则需要20个核心线程。

5.2 queueSize

queueSize与coreSize和任务的最大响应时间有关。假设系统要求一个任务最多1s执行完毕,那么queueSize的计算方法:

1000-queueSize

参考:美团点评技术文章:Java线程池实现原理及其在美团业务中的实践

Java线程池ThreadPoolExecutor原理