线程池是存放线程的容器,内部维护了若干个线程。通过利用线程池可以避免频繁创建线程,销毁线程带来的系统内耗,提高吞吐量。在Java中用Thread
对线程做了抽象,线程池的实现类是ThreadPoolExecutor
。但是线程之间的切换需要系统调用进内核,一旦线程池中线程的数量比较多,线程切换带来的内耗会制约系统吞吐量。协程(在Windows上称为纤程)本质上是用户态的线程,协程的调度不需要进内核,在用户态即可完成,所以相对线程,协程更加轻量。在Java中Quasar、 Loom库中实现了协程。
Java线程池类结构
下图是Java中关于线程池的类的组织架构。顶层接口是Executor
,里面只有一个execute(Runnable command)方法
,ExecutorService
扩展了顶层接口,添加了关闭线程池等方法,AbstractExecutorService
实现了ExecutorService
中的部分方法。而真正要用到的类是ThreadPoolExecutor
。
ThreadPoolExecutor部分源码解读
属性
ThreadPoolExecutor
主要属性包括线程池的状态以及线程池中工作线程的数量。ThreadPoolExecutor
构造函数中包含了如下变量:
corePoolSize
核心线程数量maximumPoolSize
线程池所允许的最大线程数量keepAliveTime
线程存活时间(一般指corePoolSize
和maximumPoolSize
这些线程的存活时间,当调用allowCoreThreadTimeout()
后,核心线程也可以超时退出)TimeUnit
超时单位(毫秒、秒等)BlockingQueue
阻塞队列用于存放任务ThreadFactory
创建线程的工厂,可以用来根据业务用来设置线程的名字
线程池状态属性
1 | // 工具数字,29。后面会用它进行位运算,创建线程池的状态 |
线程池状态转换
因为在创建工作线程以及任务执行过程中需要不断地的判断线程池状态,所以有必要了解:)
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 emptySTOP -> TIDYING
:When pool is empty因为
STOP
状态不会在去处理队列中的任务,所以只需要考虑线程池中的线程数量TIDYING -> TERMINATED
:When the terminated() hook method has completed
执行任务源码
public void execute(Runnable command)
在将来某个时间执行给定的任务(将来的意思是任务可能会放到任务队列中),可能创建线程执行也可能利用现有的线程执行。
1 | public void execute(Runnable command) { |
private boolean addWorker(Runnable firstTask, boolean core)
创建工作线程,每个 Worker 内部维护一个Thread,也可以想成每个 Worker 是对 Thread 的抽象
1 | private boolean addWorker(Runnable firstTask, boolean core) { |
private void addWorkerFailed(Worker w)
处理 [创建工作线程出现的失败情况],会将之前放到集合中的Worker
对象移除,并且将ctl
变量中保存的工作线程数量减1
1 | private void addWorkerFailed(Worker w) { |
public void run()
创建的线程会从run方法开始执行
1 | // 作为runWorker(Worker w)的代理方法,在Worker中实现 |
private Runnable getTask()
线程会不断地尝试获取任务,当取任务超时后,会在符合预先条件的情况下结束当前线程:
此时getTask()
会返回null,线程结束在runWorker(Worker w)
中的while
循环。
1 | private Runnable getTask() { |
public interface RejectedExecutionHandler
ThreadPoolExecutor
基于RejectedExecutionHandler
提供了四种拒绝策略,自己也可以基于RejectedExecutionHandler
定制拒绝策略。
1 | // 让提交任务的线程去执行 |
关闭线程池
1 | public void shutdown() { |
ScheduledThreadPoolExecutor 源码解读
ScheduledThreadPoolExecutor
为定时线程池, 可以周期性的执行任务,文档:
Q&A
线程池工作流程?
线程池的种类?
线程池的种类也就是线程池工具类 Executors
中提供的线程池,实际上也就是通过调整ThreadPoolExecutor
以及ScheduledThreadPoolExecutor
构造函数中的参数。不过阿里巴巴手册中不建议用这个工具类,所以就简单看一下在ThreadPoolExectuor
的构造函数中如何“搭配”变量。
固定数量的线程池:核心线程和最大线程数量一样。
1
return new ThreadPoolExecutor(nThreads, nThreads, 0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<Runnable>());
单线程线程池:核心线程和最大线程数量都为1
1
return new FinalizableDelegatedExecutorService (new ThreadPoolExecutor(1, 1, 0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<Runnable>()));
缓存线程池:最大线程数量为
Integer.MAX_VALUE
也可以认为是无界的,尽管会提高性能,但一般不会用这个,因为会创建大量的线程执行。1
return new ThreadPoolExecutor(0, Integer.MAX_VALUE, 60L, TimeUnit.SECONDS, new SynchronousQueue<Runnable>());
定时线程池
1
return new ScheduledThreadPoolExecutor(corePoolSize, threadFactory);
线程在执行任务中出现异常会怎么办?
参照runWorker()
中,当线程在执行任务时抛出了异常,会触发processWorkerExit(w, completedAbruptly);
此时当前线程结束,如果线程池没有关闭,会重新创建一个线程代替当前线程
执行拒绝策略的时刻?
- 达到边界条件:任务队列满了,工作线程数量达到最大线程数量。
- 非边界条件:当工作线程数量达到了核心线程数量,此时如果入队成功 => 如果线程池已经关了,会尝试移除刚才入队的任务。如果移除成功 => 会执行拒绝策略
拒绝策略使用注意事项?
注意在使用 Future 同时使用 Discard 策略时,注意设置超时时间,因为当执行了这个策略后,后续又执行了 Future.get(),此时会卡住当前线程,所以需要加上超时时间或者使用其他拒绝策略