线程池的五种状态
使用AtomicInteger类型的变量ctl的高三位表示线程池的状态,其它位数表示线程池中工作线程的数量。
- RUNNING
- 接受新的任务和处理队列中的任务
- 线程池正常运行中,可以正常的接受并处理任务
- SHUTDOWN
- 线程池关闭了,不能接受新任务,但是线程池会把阻塞队列中的剩余任务执行完,剩余任务都处理完之后,会中断所有工作线程
- STOP
- 线程池停止了,不能接受新任务,并且也不会处理阻塞队列中的任务,会中断所有工作线程
- TIDYING
- 当前线程池中的工作线程都被停止后,就会进入TIDYING
- TERMINATED
- 线程池处于TIDYING状态后,会执行terminated()方法,执行完后就会进入TERMINATED状态,在ThreadPoolExecutor中terminated()是一个空方法,可以自定义线程池重写这个方法
RUNNING | SHUTDOWN | STOP | TIDYING | TERMINATED | |
---|---|---|---|---|---|
高三位 | 111 | 000 | 001 | 010 | 011 |
接受新的任务 | yes | no | no | no | no |
处理队列中的任务 | yes | yes | no | no | no |
中断任务 | no | yes | yes | no | no |
触发时机 | execute() | shutdown() | shutdownNow() | 状态为SHUTDOWN或STOP,线程池中没有线程时自动转换 | 状态为TIDYING,terminated()执行完后就会自动转换 |
public class ThreadPoolExecutorDemo {
private final AtomicInteger ctl = new AtomicInteger(ctlOf(RUNNING, 0));
private static final int COUNT_BITS = Integer.SIZE - 3;
private static final int COUNT_MASK = (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;
private static int ctlOf(int rs, int wc) { return rs | wc; }
static String formatState(int state) {
return String.format("%32s", Integer.toBinaryString(state)).replace(' ', '0');
}
void printState() {
System.out.println("工作线程数标识位:" + COUNT_BITS);
System.out.println("线程池的状态 RUNNING:" + formatState(RUNNING));
System.out.println("线程池的状态 SHUTDOWN:" + formatState(SHUTDOWN));
System.out.println("线程池的状态 STOP:" + formatState(STOP));
System.out.println("线程池的状态 TIDYING:" + formatState(TIDYING));
System.out.println("线程池的状态 TERMINATED:" + formatState(TERMINATED));
}
}
线程池的主要方法
mainLock,它是线程池中的一把全局锁,主要是用来控制workers集合的并发安全。
execute
- 工作线程数小于corePoolSize,则添加工作线程,并把command作为该线程要执行的任务 addWorker(command, true)
- 线程池状态是RUNNING,把任务添加到阻塞队列中 workQueue.offer(command)
- 加入队列成功后还会获取线程池状态,如果不是RUNNING,移除任务成功后执行reject(command)
- 如果工作线程为0,addWorker(null, false)
addWorker
核心逻辑就是:
- 先判断工作线程数是否超过了限制
- 修改ctl,使得工作线程数+1
- 构造Work对象,并把它添加到workers集合中
- 启动Work对象对应的工作线程
runWorker
processWorkerExit
某个工作线程正常情况下会不停的循环从阻塞队列中获取任务来执行,正常情况下就是通过阻塞来保证线程永远活着,但是会有一些特殊情况:
- 如果线程被中断了,那就会退出循环,然后做一些善后处理,比如ctl中的工作线程数-1,然后自己运行结束
- 如果线程阻塞超时了,那也会退出循环,此时就需要判断线程池中的当前工作线程够不够,比如是否有corePoolSize个工作线程,如果不够就需要新开一个线程,然后当前线程自己运行结束,这种看上去效率比较低,但是也没办法,当然如果当前工作线程数足够,那就正常,自己正常的运行结束即可
- 如果线程是在执行任务的时候抛了移除,从而退出循环,那就直接新开一个线程作为替补,当然前提是线程池的状态是RUNNING
getTask
只有通过调用线程池的shutdown方法或shutdownNow方法才能真正中断线程池中的线程。
在java,中断一个线程,只是修改了该线程的一个标记,并不是直接kill了这个线程,被中断的线程到底要不要消失,由被中断的线程自己来判断,比如上面代码中,线程遇到了中断异常,它可以选择什么都不做,那线程就会继续进行外层循环,如果选择return,那就退出了循环,后续就会运行结束从而消失。
shutdown
调用线程池的shutdown方法,表示要关闭线程池,不接受新任务,但是要把阻塞队列中剩余的任务执行完。
根据前面execute方法的源码,只要线程池的状态不是RUNNING,那么就表示线程池不接受新任务,所以shutdown方法要做的第一件事情就是修改线程池状态。
那第二件事情就是要中断线程池中的工作线程,这些工作线程要么在执行任务,要么在阻塞等待任务:
- 对于在阻塞等待任务的线程,直接中断即可,
- 对于正在执行任务的线程,其实只要等它们把任务执行完,就可以中断了,因为此时线程池不能接受新任务,所以正在执行的任务就是最后剩余的任务
shutdownNow
public void shutdown() {
final ReentrantLock mainLock = this.mainLock;
mainLock.lock();
try {
checkShutdownAccess();
advanceRunState(SHUTDOWN);
interruptIdleWorkers();
onShutdown(); // hook for ScheduledThreadPoolExecutor
} finally {
mainLock.unlock();
}
tryTerminate();
}
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;
}