ThreadPoolExecutor系列之状态详解

状态定义

线程池ThreadPoolExecutor有两种属性:运行状态和线程数。

线程数很好理解,就是池子中有多少线程;运行状态共有5种,分别是

RUNNING:运行中, 为初始状态,即刚创建的线程池就是此状态。
SHUTDOWN:停工状态,不再接收新任务,但会继续处理队列中的任务。
STOP:停止状态,不再接收新任务,不再处理已有任务,且会中断正在执行的任务。
TIDYING:清理中,所有任务都停止了,且线程数量也降为0。
TERMINATED,终止状态,钩子函数terminated()已经执行完成,线程池彻底销毁。
复制代码

状态流转

五种状态的流转图如下

源码注释写的非常棒,此处贴一下

*   RUNNING:  Accept new tasks and process queued tasks
*   SHUTDOWN: Don't accept new tasks, but process queued tasks
*   STOP:     Don't accept new tasks, don't process queued tasks,
*             and interrupt in-progress tasks
*   TIDYING:  All tasks have terminated, workerCount is zero,
*             the thread transitioning to state TIDYING
*             will run the terminated() hook method
*   TERMINATED: terminated() has completed
*
* The numerical order among these values matters, to allow
* ordered comparisons. The runState monotonically increases over
* time, but need not hit each state. The transitions are:
*
* RUNNING -> SHUTDOWN
*    On invocation of shutdown(), perhaps implicitly in finalize()
* (RUNNING or SHUTDOWN) -> STOP
*    On invocation of shutdownNow()
* SHUTDOWN -> TIDYING
*    When both queue and pool are empty
* STOP -> TIDYING
*    When pool is empty
* TIDYING -> TERMINATED
*    When the terminated() hook method has completed
复制代码

代码实现

线程数是一个非负整数,可以用一个int来表示。

运行状态固定只有5种,为节省空间,可以用三个bit来表示,因为三个bit最多可以表示8种状态了。

线程池的这两个属性是需要同时访问的,也就是需要保证原子操作,如果按上面分开表示的话,修改时估计还得加一把锁。

那有没有更优雅的实现呢,我们来看一下Doug Lea大牛的实现吧

/**
 * The main pool control state, ctl, is an atomic integer packing
 * two conceptual fields
 *   workerCount, indicating the effective number of threads
 *   runState,    indicating whether running, shutting down etc
 */
private final AtomicInteger ctl = new AtomicInteger(ctlOf(RUNNING, 0));
// 表示线程数的bit数
private static final int COUNT_BITS = Integer.SIZE - 3;
// 线程池支持的最大的线程数量(536870911,完全够用了)
private static final int CAPACITY   = (1 << COUNT_BITS) - 1;

// runState is stored in the high-order bits(运行状态保存在int的高三位)
// 通过注释的数字可以看出,运行状态对应的数字依次增大,这很重要,方便后续的运行状态判断
// 1110 0000 0000 0000 0000 0000 0000 0000(-536870912)
private static final int RUNNING    = -1 << COUNT_BITS;
// 0000 0000 0000 0000 0000 0000 0000 0000(0)
private static final int SHUTDOWN   =  0 << COUNT_BITS;
// 0010 0000 0000 0000 0000 0000 0000 0000(536870912)
private static final int STOP       =  1 << COUNT_BITS;
// 0100 0000 0000 0000 0000 0000 0000 0000(1073741824)
private static final int TIDYING    =  2 << COUNT_BITS;
// 0110 0000 0000 0000 0000 0000 0000 0000(1610612736)
private static final int TERMINATED =  3 << COUNT_BITS;

// 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; }

/*
 * 因为运行状态存在int的高三位,且依次增大,因此可以巧妙的通过大小比较得知两状态关系
 */
private static boolean runStateLessThan(int c, int s) {
    return c < s;
}

private static boolean runStateAtLeast(int c, int s) {
    return c >= s;
}
// 判断线程是否在运行
// 此处为啥用 < SHUTDOWN 而不是直接 = RUNNING 呢,因为ctl还有低29位表示的线程数呢
private static boolean isRunning(int c) {
    return c < SHUTDOWN;
}
复制代码

Doug Lea巧妙的使用了位运算,用一个AtomicInteger解决了操作两个属性的原子性问题;同时把状态放在高三位,还可以方便的通过大小比较来的值线程池的状态。

关于为什么使用int而不是long,Doug Lea也很贴心的给出了解释

* In order to pack them into one int, we limit workerCount to
* (2^29)-1 (about 500 million) threads rather than (2^31)-1 (2
* billion) otherwise representable. If this is ever an issue in
* the future, the variable can be changed to be an AtomicLong,
* and the shift/mask constants below adjusted. But until the need
* arises, this code is a bit faster and simpler using an int.
复制代码

用29位表示线程数,已经可以支持5亿个线程了,一般我们会通过-Xss1024K来设置线程栈大小为1M,如果我们达到5亿个线程,光线程栈空间就需要占用至少500T的内存了,真的难以想象我们有一天能到达这个量级。

不过Doug Lea还是很严谨的考虑了这种可能,如果AtomicInteger不够用了,就改用AtomicLong,不过目前来说用AtomicInteger更快更简单,不过度设计,够用就好~

状态操作

状态判断

public boolean isShutdown() {
    return ! isRunning(ctl.get());
}

public boolean isTerminating() {
    int c = ctl.get();
    return ! isRunning(c) && runStateLessThan(c, TERMINATED);
}

public boolean isTerminated() {
    return runStateAtLeast(ctl.get(), TERMINATED);
}
复制代码

有了以上关于状态定义的解释,再看这里关于线程池状态的判断方法就很好理解了。

isShutdown:只要线程池不是RUNNING状态,便是关闭了。

isTerminating:终止中,既不是RUNNING状态,又没有到终止状态。

isTerminated:已终止,那高三位必须是011,因此ctl的值要大于等于TERMINATED。

添加工作线程addWorker

当新任务到来时,如果核心线程数未满或阻塞队列已满,都会添加工作线程,即调用addWorker方法。addWorker方法有两个入参,第一个是Runnable任务,即最新到来的这个任务,不过该参数可能为空;第二个参数表示添加的是否是核心线程。

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

        // step 1
        if (rs >= SHUTDOWN &&
            ! (rs == SHUTDOWN &&
               firstTask == null &&
               ! workQueue.isEmpty()))
            return false;

        for (;;) {
            int wc = workerCountOf(c);
            // step 2.1
            if (wc >= CAPACITY ||
                wc >= (core ? corePoolSize : maximumPoolSize))
                return false;
            // step 2.2
            if (compareAndIncrementWorkerCount(c))
                break retry;
            c = ctl.get();  // Re-read ctl
            // step 2.3
            if (runStateOf(c) != rs)
                continue retry;
            // step 2.4
            // else CAS failed due to workerCount change; retry inner loop
        }
    }

    // step 3
    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());

                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();
            }
            if (workerAdded) {
                t.start();
                workerStarted = true;
            }
        }
    } finally {
        if (! workerStarted)
            addWorkerFailed(w);
    }
    return workerStarted;
}
复制代码

接下来,我们依次看下具体的处理过程。

首先是一个带有retry标号的死循环,在java中标号一般用在多重循环中,用于方便的控制程序流向,此处正是一个双重死循环。

Step 1,在外层死循环中,判断了线程池的状态,

if (rs >= SHUTDOWN && ! (rs == SHUTDOWN && firstTask == null && ! workQueue.isEmpty()))
复制代码

这个判断条件写的很复杂,Doug Lea的风格就喜欢把很多处理逻辑写到一条大的判断逻辑中,不过也导致了代码理解起来很困难,这里我们把这个判断条件根据德摩根定律做下转换,就是

// 根据德摩根定律转换
if (rs >= SHUTDOWN && (rs != SHUTDOWN || firstTask != null || workQueue.isEmpty()))
// 继续化简,去除括号,变成了三个条件的或运算,即满足三者中任意其一,都将添加失败
if((rs >= SHUTDOWN && rs != SHUTDOWN) ||
   (rs >= SHUTDOWN && firstTask != null) ||
   (rs >= SHUTDOWN && workQueue.isEmpty())) return false;
复制代码

简化完看起来就好理解一些了,我们分开来看三个子条件

条件一,(rs >= SHUTDOWN && rs != SHUTDOWN) 等价于 rs > SHUTDOWN,即线程池处于STOP、TIDYING、TERMINATED三个状态,根据开头状态定义,都不会再处理任务了,所以addWorker肯定是要失败的。

条件二,(rs >= SHUTDOWN && firstTask != null),SHUTDOWN是停工状态,不再接收新任务,firstTask不为空说明正在添加新任务,当然也要制止,更不用说rs > SHUTDOWN的情况了(见条件一)。

条件三,(rs >= SHUTDOWN && workQueue.isEmpty()),任务队列都空了,说明没有任务要做了,且线程池已经马上或者已经关闭了,那还添加工作线程干嘛,不用说,自然也要劝退。

Step 2.1,跨过外层循环中的状态判断这一关之后,就进入到了内层死循环,有一点层层闯关的味儿。在这一关内,首先判断线程数wc,如果入参core是true,就判断核心线程数是否已达上限,如果core为false就去判断最大线程数是否已达到,只要已达上限就都不能再继续添加线程了。

Step 2.2,通过CAS方式给ctl加一,也就是线程数加一。只要CAS返回成功,就可以跳出retry双重循环进入到下一关了(step 3)。

Step 2.3,CAS失败,只有一种可能,就是同时有其他线程也修改了ctl值。我们知道ctl包含了两个属性:运行状态和线程数,也就是说这两个属性都有可能发生了变化。因此先判断线程池状态是否发生了变化,如果变了,就重新回到第一关(retry外层循环),一夜回到解放前~

Step 2.4,这一步虽然没有对应的代码,但是也是有含义的,即ctl中的线程数改变了。这时候就没必要重新回到第一关,只需要从内层循环重新开始就可以了。

Step 3,ctl线程数已经增加1了,也不能光吆喝不干事啊,所以就开始真正的添加worker了。这一步比较简单,创建worker对象并添加到workers集合中。这一步加锁了,因为要重新检查ctl状态,并且要往workers这个HashSet中添加元素,我们知道HashSet不是线程安全的。

/**
 * Set containing all worker threads in pool. Accessed only when
 * holding mainLock.
 */
private final HashSet<Worker> workers = new HashSet<Worker>();
复制代码

工作线程Worker

经过层层闯关才能创建成功的worker到底长什么样呢,现在就开始揭开庐山真面目了~

private final class Worker extends AbstractQueuedSynchronizer implements Runnable
{
    /**
     * This class will never be serialized, but we provide a
     * serialVersionUID to suppress a javac warning.
     */
    private static final long serialVersionUID = 6138294804551838833L;

    /** Thread this worker is running in.  Null if factory fails. */
    final Thread thread;
    /** Initial task to run.  Possibly null. */
    Runnable firstTask;
    /** Per-thread task counter */
    volatile long completedTasks;

    /**
     * Creates with given first task and thread from ThreadFactory.
     * @param firstTask the first task (null if none)
     */
    Worker(Runnable firstTask) {
        setState(-1); // inhibit interrupts until runWorker
        this.firstTask = firstTask;
        this.thread = getThreadFactory().newThread(this);
    }

    /** Delegates main run loop to outer runWorker  */
    public void run() {
        runWorker(this);
    }

    // Lock methods
    //
    // The value 0 represents the unlocked state.
    // The value 1 represents the locked state.

    protected boolean isHeldExclusively() {
        return getState() != 0;
    }

    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;
    }

    public void lock()        { acquire(1); }
    public boolean tryLock()  { return tryAcquire(1); }
    public void unlock()      { release(1); }
    public boolean isLocked() { return isHeldExclusively(); }

    void interruptIfStarted() {
        Thread t;
        if (getState() >= 0 && (t = thread) != null && !t.isInterrupted()) {
            try {
                t.interrupt();
            } catch (SecurityException ignore) {
            }
        }
    }
}
复制代码

Worker类实现了Runnable接口,同时继承了AQS抽象类,因此它也维护了一个同步状态state,取值共有三个:

-1: 初始状态,在Worker构造方法中赋值的,即刚new出来,还没有run起来
0: 无锁状态,后面可以看到,在非执行任务期间处于无锁状态
1: 加锁状态,处于加锁状态说明worker正在工作(执行提交的任务)
复制代码

tryAcquire方法实现中,只有在state是0的时候才能返回成功,说明不是可重入的。

interruptIfStarted方法,顾名思义就是如果worker启动了就中断掉,那怎么知道worker有没有start呢?通过state的三个状态就可以知道了,如果没有start,则state是初始值-1。所以方法中有一个判断就是state>=0,如果是非负数说明worker已经启动了。

看完Worker类,发现没有一个关于是否是核心线程的变量,那核心线程和非核心线程怎么区分呢?其实Doug Lea在线程池的实现中,所有的工作线程没有任何区别,核心线程数和最大线程数仅仅是用来控制线程池的一些处理流程的,比如上面的addWorker和下面的getTask。

Worker类中持有一个thread变量,thread变量是这么创建出来的:

this.thread = getThreadFactory().newThread(this);
复制代码

因此当线程start的时候就会执行Worker类的run方法,run方法又直接委托给了ThreadPoolExecutor的runWorker方法。

runWorker

final void runWorker(Worker w) {
    Thread wt = Thread.currentThread();
    Runnable task = w.firstTask;
    w.firstTask = null;
    // step 1
    w.unlock(); // allow interrupts
    boolean completedAbruptly = true;
    try {
        // step 2
        while (task != null || (task = getTask()) != null) {
            // step 3
            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 {
                    // step 4
                    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;
                // step 5
                w.completedTasks++;
                w.unlock();
            }
        }
        completedAbruptly = false;
    } finally {
        // step 6
        processWorkerExit(w, completedAbruptly);
    }
}
复制代码

runWorker方法比较长,这里把关键的6步标了出来。

Step 1,既然已经run起来了,那首先unlock一下,即把state状态从初值-1置为0,以免后面无法加锁(上面也提到过,只有state是0的时候才能加锁成功)。

Step 2,取任务,task要么是创建worker时传入的第一个任务,要么就去阻塞队列中抢任务。

Step 3,取到任务之后马上要开始干活了,先加下锁,告诉别人我正在忙呢,就好比上厕所把门锁上了。

Step 4,真正干活,不能占着茅坑不拉屎啊~

Step 5,干完活了,成功完成一个任务,鸡腿加一,然后解锁,好让别人知道我闲下来了。

Step 6,while循环退出了,说明没有取到任务,要么是线程池关闭了,要么是非核心线程在超时时间内没有取到任务,不管是哪种情况,worker都得被销毁了。

getTask

线程池能保证线程复用的一个核心思想就是,线程不断的去获取任务并执行,保证线程一直存活。那接下来,就来看一下工作线程是如何来取任务的。

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);

        // Are workers subject to culling?
        boolean timed = allowCoreThreadTimeOut || wc > corePoolSize;

        if ((wc > maximumPoolSize || (timed && timedOut))
            && (wc > 1 || workQueue.isEmpty())) {
            if (compareAndDecrementWorkerCount(c))
                return null;
            continue;
        }

        try {
            Runnable r = timed ?
                workQueue.poll(keepAliveTime, TimeUnit.NANOSECONDS) :
                workQueue.take();
            if (r != null)
                return r;
            timedOut = true;
        } catch (InterruptedException retry) {
            timedOut = false;
        }
    }
}
复制代码

这里简单讲一下该方法的思路。方法里又是一个死循环,每次循环都会先判断线程池状态,如果已关闭,则直接给线程数减一并返回null,让工作线程退出。然后判断是否需要带超时时间,如果带超时时间且超时未取到任务就会进入下次循环,在下次循环中就要有一个线程退出了,因为已经超过了最大空闲存活时间了。那怎么保证并发情况下只有一个线程退出呢,核心就在于

if (compareAndDecrementWorkerCount(c)) return null;
复制代码

即通过CAS方式给线程数减一,只有CAS成功的线程才会退出,失败的线程则进入下次循环。而下次循环时,由于线程数减一了,timed变量就可能不再是true了。通过这种方式动态的维持线程数,而不需要每个worker维持一个核心标识,简化了实现,实在是妙~

processWorkerExit

private void processWorkerExit(Worker w, boolean completedAbruptly) {
    if (completedAbruptly) // If abrupt, then workerCount wasn't adjusted
        decrementWorkerCount();

    final ReentrantLock mainLock = this.mainLock;
    mainLock.lock();
    try {
        completedTaskCount += w.completedTasks;
        workers.remove(w);
    } finally {
        mainLock.unlock();
    }

    tryTerminate();

    int c = ctl.get();
    if (runStateLessThan(c, STOP)) {
        if (!completedAbruptly) {
            int min = allowCoreThreadTimeOut ? 0 : corePoolSize;
            if (min == 0 && ! workQueue.isEmpty())
                min = 1;
            if (workerCountOf(c) >= min)
                return; // replacement not needed
        }
        addWorker(null, false);
    }
}
复制代码

在worker退出时,主要做以下几件事:

  1. 如果completedAbruptly为true,即worker是异常退出,则给线程数减一;因为正常退出的话,getTask方法已经减一了。
  2. 将当前worker的完成任务数累加到线程池的完成任务数上,用于统计信息;将worker从workers集合中移除;
  3. 有worker退出了,说明线程池可能有状况发生,因此尝试关闭一下线程池(下面还会有详细解释~)。
  4. 只要线程池还未到STOP状态,要么worker是异常退出,要么正常退出但没有足够的worker来处理已有任务,这两种情况都需要添加新的worker来补位,以免影响任务的处理。

关闭线程池

关闭线程池有两种方式,分别是

  1. shutdown:将线程池状态改为SHUTDOWN,并中断所有空闲线程,可以看到并没有停止已有任务的执行。
  2. shutdownNow:将线程池状态改为STOP,中断所有线程,不再处理任何任务,并将未处理的任务打包返回。

shutdown

下面先来看shutdown方法

public void shutdown() {
    final ReentrantLock mainLock = this.mainLock;
    mainLock.lock();
    try {
        checkShutdownAccess();
        advanceRunState(SHUTDOWN);
        interruptIdleWorkers();
        onShutdown(); // hook for ScheduledThreadPoolExecutor
    } finally {
        mainLock.unlock();
    }
    tryTerminate();
}

private void advanceRunState(int targetState) {
    for (;;) {
        int c = ctl.get();
        if (runStateAtLeast(c, targetState) ||
            ctl.compareAndSet(c, ctlOf(targetState, workerCountOf(c))))
            break;
    }
}

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();
    }
}

final void tryTerminate() {
    for (;;) {
        int c = ctl.get();
        if (isRunning(c) ||
            runStateAtLeast(c, TIDYING) ||
            (runStateOf(c) == SHUTDOWN && ! workQueue.isEmpty()))
            return;
        if (workerCountOf(c) != 0) { // Eligible to terminate
            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
    }
}
复制代码

shutdown这会依次调用以下几个方法

  1. advanceRunState,还是通过死循环+CAS来修改状态。
  2. interruptIdleWorkers,通过tryLock得知加锁状态从而判断线程是否空闲,如果空闲就中断。
  3. onShutdown,钩子函数,留给子类进行扩展用。
  4. tryTerminate,这个方法稍微复杂些,下面解释下。

tryTerminate方法里又是一个死循环,首先还是判断下线程池的状态,如果是RUNNING或者SHUTDOWN但还有任务未处理,不能关闭;如果是TIDYING或TERMINATED,则没必要再次终止了,也直接返回。

如果线程数不为0,就中断一个空闲的worker,然后返回。这里为什么只中断一个空闲线程呢,主要是有一种场景:shutdown方法只中断空闲线程,如果线程正在执行任务则不会被中断,但是当这些线程执行完所有任务最终还是会变成空闲线程,从而执行processWorkerExit方法。上面看到了,processWorkerExit方法会调用tryTerminate方法,从而不断传播,最终将所有空闲线程中断。

接下来就很简单了,将状态CAS为TIDYING,CAS成功后就执行钩子函数terminated。执行完terminated方法后就可以将状态置为TERMINATED了,这也符合了开篇的状态转换图了。如果CAS失败,则进入下一次循环,继续各种状态判断。

当ctl状态置为TERMINATED之后,就通过termination.signalAll()来唤醒等待在termination条件队列上的线程了。等待的线程是什么,当然是调用线程池awaitTermination方法的线程了~

public boolean awaitTermination(long timeout, TimeUnit unit)
    throws InterruptedException {
    long nanos = unit.toNanos(timeout);
    final ReentrantLock mainLock = this.mainLock;
    mainLock.lock();
    try {
        for (;;) {
            if (runStateAtLeast(ctl.get(), TERMINATED))
                return true;
            if (nanos <= 0)
                return false;
            nanos = termination.awaitNanos(nanos);
        }
    } finally {
        mainLock.unlock();
    }
}
复制代码

shutdownNow

public List<Runnable> shutdownNow() {
    List<Runnable> tasks;
    final ReentrantLock mainLock = this.mainLock;
    mainLock.lock();
    try {
        checkShutdownAccess();
        advanceRunState(STOP);
        interruptWorkers();
        tasks = drainQueue();
    } finally {
        mainLock.unlock();
    }
    tryTerminate();
    return tasks;
}

private void interruptWorkers() {
    final ReentrantLock mainLock = this.mainLock;
    mainLock.lock();
    try {
        for (Worker w : workers)
            w.interruptIfStarted();
    } finally {
        mainLock.unlock();
    }
}
复制代码

理解了shutdown方法之后,再来看shutdownNow就简单多了,区别主要有三个:

  1. 将线程池状态改为STOP。
  2. 中断所有worker线程,使用的是interruptIfStarted方法,即不管是否空闲。
  3. 从阻塞队列中取出所有未执行的任务并返回。

总结

  1. 线程池有5种状态,但是并不一定会都经历。
  2. Worker类继承AQS,通过加锁状态来判断是否空闲。
  3. 核心线程和非核心线程并没有做区分,只有两个数字用来控制线程池的不同处理逻辑。
  4. 看完线程池源码之后,我们会发现多线程并发处理非常复杂,有各种各样的情况需要考虑,源码中经常使用死循环+状态判断+CAS来处理。并发大牛Doug Lea为我们提供了久经考验的J.U.C并发工具包,因此一般情况下我们就不要自己造轮子了。

参考资料

  1. Java线程池实现原理及其在美团业务中的实践 - 美团技术团队
  2. 彻底理解Java线程池原理篇 - 云+社区 - 腾讯云
  3. Java多线程进阶(四十)—— J.U.C之executors框架:ThreadPoolExecutor - SegmentFault 思否

Guess you like

Origin juejin.im/post/7050670528385777700