线程池源码的基础属性和方法
在线程池的源码中,会通过一个AtomicInteger类型的变量ctl,来表示线程池的状态和当前线程池中的工作线程数量。
一个Integer占4个字节,也就是32个bit,线程池有5个状态:
- RUNNING
- SHUTDOWN
- STOP
- TIDYING
- TERMINATED
2个bit能表示4种状态,那5种状态就至少需要三个bit位,比如在线程池的源码中就是这么来表示的:
private static final int COUNT_BITS = Integer.SIZE - 3;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;
Integer.SIZE为32,所以COUNT_BITS为29,最终各个状态对应的二级制为:
- RUNNING:11100000 00000000 00000000 00000000
- SHUTDOWN:00000000 00000000 00000000 00000000
- STOP:00100000 00000000 00000000 00000000
- TIDYING:01000000 00000000 00000000 00000000
- TERMINATED:01100000 00000000 00000000 00000000
所以,只需要使用一个Integer数字的最高三个bit,就可以表示5种线程池的状态,而剩下的29个bit就可以用来表示工作线程数,比如,假如ctl为:11100000 00000000 00000000 00001010,就表示线程池的状态为RUNNING,线程池中目前在工作的线程有10个,这里说的“在工作”意思是线程活着,要么在执行任务,要么在阻塞等待任务。
同时,在线程池中也提供了一些方法用来获取线程池状态和工作线程数,比如:
// 29,二进制为00000000 00000000 00000000 00011101private static final int COUNT_BITS = Integer.SIZE - 3;// 00011111 11111111 11111111 11111111private static final int CAPACITY = (1 << COUNT_BITS) - 1;// ~CAPACITY为11100000 00000000 00000000 00000000// &操作之后,得到就是c的高3位private static int runStateOf(int c) {return c & ~CAPACITY;}// CAPACITY为00011111 11111111 11111111 11111111// &操作之后,得到的就是c的低29位private static int workerCountOf(int c) {return c & CAPACITY;}
同时,还有一个方法:
private static int ctlOf(int rs, int wc) {return rs | wc;}
就是用来把运行状态和工作线程数量进行合并的一个方法,不过传入这个方法的两个int数字有限制,rs的低29位都得为0,wc的高3位都得为0,这样经过或运算之后,才能得到准确的ctl。
同时,还有一些相关的方法:
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;// c状态是否小于s状态,比如RUNNING小于SHUTDOWNprivate static boolean runStateLessThan(int c, int s) {return c < s;}// c状态是否大于等于s状态,比如STOP大于SHUTDOWNprivate static boolean runStateAtLeast(int c, int s) {return c >= s;}// c状态是不是RUNNING,只有RUNNING是小于SHUTDOWN的private static boolean isRunning(int c) {return c < SHUTDOWN;}// 通过cas来增加工作线程数量,直接对ctl进行加1// 这个方法没考虑是否超过最大工作线程数的(2的29次方)限制,源码中在调用该方法之前会进行判断的private boolean compareAndIncrementWorkerCount(int expect) {return ctl.compareAndSet(expect, expect + 1);}// 通过cas来减少工作线程数量,直接对ctl进行减1private boolean compareAndDecrementWorkerCount(int expect) {return ctl.compareAndSet(expect, expect - 1);}
前面说到线程池有5个状态,这5个状态分别表示:
- RUNNING:线程池正常运行中,可以正常的接受并处理任务
- SHUTDOWN:线程池关闭了,不能接受新任务,但是线程池会把阻塞队列中的剩余任务执行完,剩余任务都处理完之后,会中断所有工作线程
- STOP:线程池停止了,不能接受新任务,并且也不会处理阻塞队列中的任务,会中断所有工作线程
- TIDYING:当前线程池中没有工作线程后,也就是线程池没有工作线程在运行了,就会进入TIDYING,这个状态是自动发生在SHUTDOWN和STOP之后的
- TERMINATED:线程池处于TIDYING状态后,会执行terminated()方法,执行完后就会进入TERMINATED状态,在ThreadPoolExecutor中terminated()是一个空方法,可以自定义线程池重写这个方法
到这里大概明白这几个状态即可,接下来来看看源码中是如何来维护这几个状态的。
execute方法
当执行线程池的execute方法时:
public void execute(Runnable command) {if (command == null)throw new NullPointerException();// 获取ctl// ctl初始值是ctlOf(RUNNING, 0),表示线程池处于运行中,工作线程数为0int c = ctl.get();// 工作线程数小于corePoolSize,则添加工作线程,并把command作为该线程要执行的任务if (workerCountOf(c) < corePoolSize) {// true表示添加的是核心工作线程,具体一点就是,在addWorker内部会判断当前工作线程数是不是超过了corePoolSize// 如果超过了则会添加失败,addWorker返回false,表示不能直接开启新的线程来执行任务,而是应该先入队if (addWorker(command, true))return;// 如果添加核心工作线程失败,那就重新获取ctl,可能是线程池状态被其他线程修改了// 也可能是其他线程也在向线程池提交任务,导致核心工作线程已经超过了corePoolSizec = ctl.get();}// 线程池状态是否还是RUNNING,如果是就把任务添加到阻塞队列中if (isRunning(c) && workQueue.offer(command)) {// 在任务入队时,线程池的状态可能也会发生改变// 再次检查线程池的状态,如果线程池不是RUNNING了,那就不能再接受任务了,就得把任务从队列中移除,并进行拒绝策略// 如果线程池的状态没有发生改变,仍然是RUNNING,那就不需要把任务从队列中移除掉// 不过,为了确保刚刚入队的任务有线程会去处理它,需要判断一下工作线程数,如果为0,那就添加一个非核心的工作线程// 添加的这个线程没有自己的任务,目的就是从队列中获取任务来执行int recheck = ctl.get();if (! isRunning(recheck) && remove(command))reject(command);else if (workerCountOf(recheck) == 0)addWorker(null, false);}// 如果线程池状态不是RUNNING,或者线程池状态是RUNNING但是队列满了,则去添加一个非核心工作线程// 实际上,addWorker中会判断线程池状态如果不是RUNNING,是不会添加工作线程的// false表示非核心工作线程,作用是,在addWorker内部会判断当前工作线程数已经超过了maximumPoolSize,如果超过了则会添加不成功,执行拒绝策略else if (!addWorker(command, false))reject(command);}
addWorker方法
addWorker方法是核心方法,是用来添加线程的,core参数表示条件的是核心线程还是非核心线程。
在看这个方法之前,我们不妨先自己来分析一下,什么是添加线程?
实际上就要开启一个线程,不管是核心线程还是非核心线程其实都只是一个普通的线程,而核心和非核心的区别在于:
- 如果是要添加核心工作线程,那么就得判断目前的工作线程数是否超过corePoolSize
- 如果没有超过,则直接开启新的工作线程执行任务
- 如果超过了,则不会开启新的工作线程,而是把任务进行入队
- 如果要添加的是非核心工作线程,那就要判断目前的工作线程数是否超过maximumPoolSize
- 如果没有超过,则直接开启新的工作线程执行任务
- 如果超过了,则拒绝执行任务
所以在addWorker方法中,首先就要判断工作线程有没有超过限制,如果没有超过限制再去开启一个线程。
并且在addWorker方法中,还得判断线程池的状态,如果线程池的状态不是RUNNING状态了,那就没必要要去添加线程了,当然有一种特例,就是线程池的状态是SHUTDOWN,但是队列中有任务,那此时还是需要添加添加一个线程的。
那这种特例是如何产生的呢?
我们前面提到的都是开启新的工作线程,那么工作线程怎么回收呢?不可能开启的工作线程一直活着,因为如果任务由多变少,那也就不需要过多的线程资源,所以线程池中会有机制对开启的工作线程进行回收,如何回收的,后文会提到,我们这里先分析,有没有可能线程池中所有的线程都被回收了,答案的是有的。
首先非核心工作线程被回收是可以理解的,那核心工作线程要不要回收掉呢?其实线程池存在的意义,就是提交生成好线程资源,需要线程的时候直接使用就可以,而不需要临时去开启线程,所以正常情况下,开启的核心工作线程是不用回收掉的,就算暂时没有任务要处理,也不用回收,就让核心工作线程在那等着就可以了。
但是!在线程池中有这么一个参数:allowCoreThreadTimeOut,表示是否允许核心工作线程超时,意思就是是否允许核心工作线程回收,默认这个参数为false,但是我们可以调用allowCoreThreadTimeOut(boolean value)来把这个参数改为true,只要改了,那么核心工作线程也就会被回收了,那这样线程池中的所有工作线程都可能被回收掉,那如果所有工作线程都被回收掉之后,阻塞队列中来了一个任务,这样就形成了特例情况。
private boolean addWorker(Runnable firstTask, boolean core) {retry:for (;;) {int c = ctl.get();int rs = runStateOf(c);// 线程池的状态不是RUNNING,并且不是特例情况(线程池状态是SHUTDOWN并且队列不为空),那就直接return false,表示不新建工作线程// 如果是RUNNING,或者是特例情况,就准备新建工作线程// Check if queue empty only if necessary.if (rs >= SHUTDOWN &&! (rs == SHUTDOWN &&firstTask == null &&! workQueue.isEmpty()))return false;// 判断工作线程数是否超过了限制// 如果超过限制了,则return false// 如果没有超过限制,则修改ctl,增加工作线程数,cas成功则退出外层retry循环,去创建新的工作线程// 如果cas失败,则表示有其他线程也在提交任务,也在增加工作线程数,此时重新获取ctl// 如果发现线程池的状态发生了变化,则继续回到retry,重新判断线程池的状态是不是RUNNING// 如果状态没有变化,则继续利用cas来增加工作线程数,直到cas成功for (;;) {int wc = workerCountOf(c);if (wc >= CAPACITY ||wc >= (core ? corePoolSize : maximumPoolSize))return false;if (compareAndIncrementWorkerCount(c))break retry;c = ctl.get(); // Re-read ctlif (runStateOf(c) != rs)continue retry;// else CAS failed due to workerCount change; retry inner loop}}// ctl修改成功,也就是工作线程数+1成功// 接下来就要开启一个新的工作线程了boolean workerStarted = false;boolean workerAdded = false;Worker w = null;try {// Worker实现了Runnable接口// 在构造一个Worker对象时,就会利用ThreadFactory新建一个线程// Worker对象有两个属性:// Runnable firstTask:表示Worker待执行的第一个任务,第二个任务会从阻塞队列中获取// Thread thread:表示Worker对应的线程,就是这个线程来获取并执行任务的w = new Worker(firstTask);// 拿出线程对象,还没有startfinal 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,但是当前线程没有自己的第一个任务,那就表示当前调用addWorker方法是为了从队列中获取任务来执行// 正常情况下线程池的状态如果是SHUTDOWN,是不能创建新的工作线程的,但是队列中如果有任务,那就是上面说的特例情况if (rs < SHUTDOWN ||(rs == SHUTDOWN && firstTask == null)) {// 如果Worker对象对应的线程已经在运行了,那就有问题,直接抛异常if (t.isAlive()) // precheck that t is startablethrow new IllegalThreadStateException();// workers用来记录当前线程池中工作线程workers.add(w);// largestPoolSize用来跟踪线程池在运行过程中工作线程数的峰值int s = workers.size();if (s > largestPoolSize)largestPoolSize = s;workerAdded = true;}} finally {mainLock.unlock();}// 运行线程if (workerAdded) {t.start();workerStarted = true;}}} finally {// 在上述过程中如果抛了异常,需要从works中移除所添加的work,并且还要修改ctl,工作线程数-1,表示新建工作线程失败if (! workerStarted)addWorkerFailed(w);}// 最后表示添加工作线程成功return workerStarted;}
所以,对于addWorker方法,核心逻辑就是:
- 先判断工作线程数是否超过了限制
- 修改ctl,使得工作线程数+1
- 构造Work对象,并把它添加到workers集合中
- 启动Work对象对应的工作线程
runWorker方法
那工作线程在运行过程中,到底在做什么呢?
我们看看Work的构造方法:
Worker(Runnable firstTask) {setState(-1); // inhibit interrupts until runWorkerthis.firstTask = firstTask;this.thread = getThreadFactory().newThread(this);}
在利用ThreadFactory创建线程时,会把this,也就是当前Work对象作为Runnable传给线程,所以工作线程运行时,就会执行Worker的run方法:
public void run() {// 这个方法就是工作线程运行时的执行逻辑runWorker(this);}
final void runWorker(Worker w) {// 就是当前工作线程Thread wt = Thread.currentThread();// 把Worker要执行的第一个任务拿出来Runnable task = w.firstTask;w.firstTask = null;// 这个地方,后面单独分析中断的时候来分析w.unlock(); // allow interruptsboolean completedAbruptly = true;try {// 判断当前线程是否有自己的第一个任务,如果没有就从阻塞队列中获取任务// 如果阻塞队列中也没有任务,那线程就会阻塞在这里// 但是并不会一直阻塞,在getTask方法中,会根据我们所设置的keepAliveTime来设置阻塞时间// 如果当前线程去阻塞队列中获取任务时,等了keepAliveTime时间,还没有获取到任务,则getTask方法返回null,相当于退出循环// 当然并不是所有线程都会有这个超时判断,主要还得看allowCoreThreadTimeOut属性和当前的工作线程数等等,后面单独分析// 目前,我们只需要知道工作线程在执行getTask()方法时,可能能直接拿到任务,也可能阻塞,也可能阻塞超时最终返回nullwhile (task != null || (task = getTask()) != null) {// 只要拿到了任务,就要去执行任务// Work先加锁,跟shutdown方法有关,先忽略,后面会分析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,最好把整篇文章都看完之后再来看这个if的逻辑// 工作线程在运行过程中// 如果发现线程池的状态变成了STOP,正常来说当前工作线程的中断标记应该为true,如果发现中断标记不为true,则需要中断自己// 如果线程池的状态不是STOP,要么是RUNNING,要么是SHUTDOWN// 但是如果发现中断标记为true,那是不对的,因为线程池状态不是STOP,工作线程是要正常工作的,不要中断掉// 就算是SHUTDOWN,也要等任务都执行完之后,线程才结束,而目前线程还在执行任务的过程中,不能中断// 所以需要重置线程的中断标记,不过interrupted方法会自动清空中断标记// 清空为中断标记后,再次判断一下线程池的状态,如果又变成了STOP,那就仍然中断自己// 中断了自己后,会把当前任务执行完,在下一次循环调用getTask()方法时,从阻塞队列获取任务时,阻塞队列会负责判断当前线程的中断标记// 如果发现中断标记为true,那就会抛出异常,最终退出while循环,线程执行结束if ((runStateAtLeast(ctl.get(), STOP) ||(Thread.interrupted() &&runStateAtLeast(ctl.get(), STOP))) &&!wt.isInterrupted())wt.interrupt();try {// 空方法,给自定义线程池来实现beforeExecute(wt, task);Throwable thrown = null;try {// 执行任务// 注意执行任务时可能会抛异常,如果抛了异常会先依次执行三个finally,从而导致completedAbruptly = false这行代码没有执行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++; // 跟踪当前Work总共执行了多少了任务w.unlock();}}// 退出了While循环// 如果是执行任务的时候抛了异常,虽然也退出了循环,但是是不会执行这行代码的,只会直接进去下面的finally块中// 所以,要么是线程从队列中获取任务时阻塞超时了从而退出了循环会进入到这里// 要么时线程在阻塞的过程中被中断了,在getTask()方法中会处理中断的情况,如果被中断了,那么getTask()方法会返回null,从而退出循环// completedAbruptly=false,表示completedAbruptly = false;} finally {// 因为当前线程退出了循环,如果不做某些处理,那么这个线程就运行结束了,就是上文说的回收掉了,线程自己运行完了也就结束了// 但是如果是由于执行任务的时候抛了异常,那么这个线程不应该直接结束,而应该继续从队列中获取下一个任务// 可是代码都执行到这里了,该怎么继续回到while循环呢,怎么实现这个效果呢?// 当然,如果是由于线程被中断了,或者线程阻塞超时了,那就应该正常的运行结束,也就是被回收掉// 只不过有一些善后工作要处理,比如修改ctl,工作线程数-1processWorkerExit(w, completedAbruptly);}}
processWorkerExit方法
private void processWorkerExit(Worker w, boolean completedAbruptly) {// 如果completedAbruptly为true,表示是执行任务的时候抛了异常,那就修改ctl,工作线程数-1// 如果completedAbruptly为false,表示是线程阻塞超时了或者被中断了,实际上也要修改ctl,工作线程数-1// 只不过在getTask方法中已经做过了,这里就不用再做一次了if (completedAbruptly) // If abrupt, then workerCount wasn't adjusteddecrementWorkerCount();final ReentrantLock mainLock = this.mainLock;mainLock.lock();try {// 当前Work要离职了,将完成的任务数累加到线程池上completedTaskCount += w.completedTasks;// 将当前Work对象从workers中移除workers.remove(w);} finally {mainLock.unlock();}// 因为当前是处理线程退出流程中,所以要尝试去看看线程池的状态是不是TIDYING,如果是则要调用terminated()tryTerminate();int c = ctl.get();// 如果线程池的状态为RUNNING或者SHUTDOWNif (runStateLessThan(c, STOP)) {// completedAbruptly为false,表示线程是阻塞超时了,或者被中断了,那就应该正常的被回收掉if (!completedAbruptly) {// 如果allowCoreThreadTimeOut为true,但是阻塞队列中还有任务,那就至少得保留一个工作线程来处理阻塞队列中的任务// 如果allowCoreThreadTimeOut为false,那min就是corePoolSize,表示至少得保留corePoolSize个工作线程活着int min = allowCoreThreadTimeOut ? 0 : corePoolSize;if (min == 0 && ! workQueue.isEmpty())min = 1;// 如果当前工作线程数大于等于min,则表示符合所需要保留的最小线程数,那就直接return,不会调用下面的addWorker方法新开一个工作线程了if (workerCountOf(c) >= min)return; // replacement not needed}// 如果线程池的状态为RUNNING或者SHUTDOWN// 如果completedAbruptly为true,表示当前线程是执行任务时抛了异常,那就得新开一个工作线程// 如果completedAbruptly为false,但是不符合所需要保留的最小线程数,那也得新开一个工作线程addWorker(null, false);}}
总结一下,某个工作线程正常情况下会不停的循环从阻塞队列中获取任务来执行,正常情况下就是通过阻塞来保证线程永远活着,但是会有一些特殊情况:
- 如果线程被中断了,那就会退出循环,然后做一些善后处理,比如ctl中的工作线程数-1,然后自己运行结束
- 如果线程阻塞超时了,那也会退出循环,此时就需要判断线程池中的当前工作线程够不够,比如是否有corePoolSize个工作线程,如果不够就需要新开一个Work(新开一个线程),然后当前线程自己运行结束,这种看上去效率比较低,但是也没办法,当然如果当前工作线程数足够,那就正常,自己正常的运行结束即可
- 如果线程是在执行任务的时候抛了移除,从而退出循环,那就直接新开一个Work,当然前提是线程池的状态是RUNNING
getTask方法
上面一直提到了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.// 如果线程池状态不是RUNNING,那就继续判断// 如果线程池状态是STOP,表示当前线程不需要处理任务了,那就修改ctl工作线程数-1// 或者线程池状态是SHUTDOWN,但是阻塞队列中为空,表示当前任务没有任务要处理了,那就修改ctl工作线程数-1// return null表示当前线程无需处理任务,可以自行运行结束if (rs >= SHUTDOWN && (rs >= STOP || workQueue.isEmpty())) {decrementWorkerCount();return null;}// 当前工作线程数int wc = workerCountOf(c);// Are workers subject to culling?// 用来判断当前线程是无限阻塞还是超时阻塞,如果一个线程超时阻塞,那么一旦超时了,那么这个线程最终就会运行结束// 如果是无限阻塞,那除非被中断了,不然这个线程就一直等着获取队列中的任务// allowCoreThreadTimeOut为true,表示线程池中的所有线程都可以被回收掉,则当前线程应该直接使用超时阻塞,一旦超时就回收// allowCoreThreadTimeOut为false,则要看当前工作线程数是否超过了corePoolSize,如果超过了,则表示超过部分的线程要用超时阻塞,一旦超时就回收boolean timed = allowCoreThreadTimeOut || wc > corePoolSize;// 如果工作线程数超过了工作线程的最大限制或者线程超时了,则要修改ctl,工作线程数减1,并且return null// return null就会导致外层的while循环退出,从而导致线程直接运行结束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;// 超时了,重新进入循环,上面的代码会判断出来当前线程阻塞超时了,最后return null,线程会运行结束timedOut = true;} catch (InterruptedException retry) {// 从阻塞队列获取任务时,被中断了,也会再次进入循环,此时并不是超时,但是重新进入循环后,会判断线程池的状态// 如果线程池的状态变成了STOP或者SHUTDOWN,最终也会return null,线程会运行结束// 但是如果线程池的状态仍然是RUNNING,那当前线程会继续从队列中去获取任务,表示忽略了本次中断// 只有通过调用线程池的shutdown方法或shutdownNow方法才能真正中断线程池中的线程timedOut = false;}}}
特别注意:只有通过调用线程池的shutdown方法或shutdownNow方法才能真正中断线程池中的线程。
因为在java,中断一个线程,只是修改了该线程的一个标记,并不是直接kill了这个线程,被中断的线程到底要不要消失,由被中断的线程自己来判断,比如上面代码中,线程遇到了中断异常,它可以选择什么都不做,那线程就会继续进行外层循环,如果选择return,那就退出了循环,后续就会运行结束从而消失。
shutdown方法
调用线程池的shutdown方法,表示要关闭线程池,不接受新任务,但是要把阻塞队列中剩余的任务执行完。
根据前面execute方法的源码,只要线程池的状态不是RUNNING,那么就表示线程池不接受新任务,所以shutdown方法要做的第一件事情就是修改线程池状态。
那第二件事情就是要中断线程池中的工作线程,这些工作线程要么在执行任务,要么在阻塞等待任务:
- 对于在阻塞等待任务的线程,直接中断即可,
- 对于正在执行任务的线程,其实只要等它们把任务执行完,就可以中断了,因为此时线程池不能接受新任务,所以正在执行的任务就是最后剩余的任务
public void shutdown() {final ReentrantLock mainLock = this.mainLock;mainLock.lock();try {checkShutdownAccess();// 修改ctl,将线程池状态改为SHUTDOWNadvanceRunState(SHUTDOWN);// 中断工作线程interruptIdleWorkers();// 空方法,给子类扩展使用onShutdown(); // hook for ScheduledThreadPoolExecutor} finally {mainLock.unlock();}// 调用terminated方法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;// 如果线程没有被中断,并且能够拿到锁,就中断线程// Worker在执行任务时会先加锁,执行完任务之后会释放锁// 所以只要这里拿到了锁,就表示线程空出来了,可以中断了if (!t.isInterrupted() && w.tryLock()) {try {t.interrupt();} catch (SecurityException ignore) {} finally {w.unlock();}}if (onlyOne)break;}} finally {mainLock.unlock();}}
不过还有一个种情况,就是目前所有工作线程都在执行任务,但是阻塞队列中还有剩余任务,那逻辑应该就是这些工作线程执行完当前任务后要继续执行队列中的剩余任务,但是根据我们看到的shutdown方法的逻辑,发现这些工作线程在执行完当前任务后,就会释放锁,那就会被中断掉,那队列中剩余的任务怎么办呢?
工作线程一旦被中断,就会进入processWorkerExit方法,根据前面的分析,我们发现,在这个方法中会会线程池状态为SHUTDOWN进行判断,会重新生成新的工作线程,那么这样就能保证队列中剩余的任务一定会被执行完。
shutdownNow方法
看懂了shutdown方法,再来看shutdownNow方法就简单了。
public List<Runnable> shutdownNow() {List<Runnable> tasks;final ReentrantLock mainLock = this.mainLock;mainLock.lock();try {checkShutdownAccess();// 修改ctl,将线程池状态改为STOPadvanceRunState(STOP);// 中断工作线程interruptWorkers();// 返回阻塞队列中剩余的任务tasks = drainQueue();} finally {mainLock.unlock();}// 调用terminated方法tryTerminate();return tasks;}
private void interruptWorkers() {final ReentrantLock mainLock = this.mainLock;mainLock.lock();try {// 中断所有工作线程,不管有没有在执行任务for (Worker w : workers)w.interruptIfStarted();} finally {mainLock.unlock();}}void interruptIfStarted() {Thread t;// 只要线程没有被中断,那就中断线程,中断的线程虽然也会进入processWorkerExit方法,但是该方法中判断了线程池的状态// 线程池状态为STOP的情况下,不会再开启新的工作线程了// 这里getState>-0表示,一个工作线程在创建好,但是还没运行时,这是state为-1,可以看看Work的构造方法就知道了// 表示一个工作线程还没开始运行,不能被中断,就算中断也没意义,都还没运行if (getState() >= 0 && (t = thread) != null && !t.isInterrupted()) {try {t.interrupt();} catch (SecurityException ignore) {}}}
mainLock
在上述源码中,发现很多地方都会用到mainLock,它是线程池中的一把全局锁,主要是用来控制works集合的并发安全,因为如果没有这把全局锁,就有可能多个线程公用同一个线程池对象,如果一个线程在提交任务,一个线程在shutdown线程池,如果不做并发控制,那就有可能线程池shutdown了,但是还有工作线程没有被中断,如果1个线程在shutdown,99个线程在提交任务,那么最终就可能导致线程池关闭了,但是线程池中的很多线程都没有停止,仍然在运行,这肯定是不行,所以需要这把全局锁来对works集合的操作进行并发安全控制。
到此,线程池中的所有核心方法的源码都分析一遍,自我感觉良好,不知道你啥感觉,哈哈,希望你能看懂,看不懂的欢迎交流。
