线程池ThreadPoolExecutor原理
核心参数如何设置
核心线程数和最大线程数
线程池中线程数量我们一般要区分任务的类型,
-
如果是cpu密集性任务那么线程数一般为cpu核数+1;
// 查看cpu核心数 int cores = Runtime.getRuntime().availableProcessors();
-
如果是IO密集型任务可以按照
cpu核数 * (1 + cpu等待时长/总时长)
cpu的等待时长其实就是除了计算之外io操作耗时,cpu等待时长和任务总时长可以通过
jvisualvm
工具来查看,cpu等待时长 = 总时间 - 总时间(CPU)
当然这只是理论值,实际项目中肯定会存在多个线程池,具体还要通过压测选出较合适的线程数。
当线程数确定后,那么如何设置核心线程数和最大线程数嘞?
其实这就主要看我们要执行的任务是不是核心业务,请求是否频繁。如果是核心业务每秒都有很高的请求那么我们就可以把核心线程数和最大线程数设置一样或者相近。如果不是核心业务,几分钟或者几十分钟才来一些请求,那么核心线程数就没必要设置过大,设置最大线程数一半 或者1/3都行。
线程空闲时间
线程空闲时间没有具体的要求,一般就设置半分钟或者一分钟都行
阻塞队列设置
队列的容量设置多大,主要就是看队列中最后一个任务的等待时长业务是否能够容忍。
我们首先要计算出每个任务的执行耗时,然后再看所有核心线程数去拿队列中的最后一个任务的耗时,业务能否接收,如果能接收那么队列长度的设置就可以。
假如现在核心线程数是10个,每个任务的耗时是1s,阻塞队列的长度是100。那么队列中最后的任务就需要等待9s,然后自己再执行1s。如果业务系统能接受这个耗时那么队列长度就不用缩短。
ThreadPoolExecutor threadPool = new ThreadPoolExecutor(10, 100, 60, TimeUnit.SECONDS, new ArrayBlockingQueue<Runnable>(100));
线程池的五种状态
线程池有五种状态:
- RUNNING:会接收新任务并且会处理队列中的任务
- SHUTDOWN:不会接收新任务并且会处理队列中的任务
- STOP:不会接收新任务并且不会处理队列中的任务,并且会中断在处理的任务(注意:一个任务能不能被中断得看任务本身)
- TIDYING:所有任务都终止了,线程池中也没有线程了,这样线程池的状态就会转为TIDYING,一旦达到此状态,就会调用线程池的terminated()
- TERMINATED:terminated()执行完之后就会转变为TERMINATED
这五种状态并不能任意转换,只会有以下几种转换情况:
- RUNNING -> SHUTDOWN:手动调用shutdown()触发,或者线程池对象GC时会调用finalize()从而调用shutdown()
- (RUNNING or SHUTDOWN) -> STOP:调用shutdownNow()触发,如果先调shutdown()紧着调shutdownNow(),就会发生SHUTDOWN -> STOP
- SHUTDOWN -> TIDYING:队列为空并且线程池中没有线程时自动转换
- STOP -> TIDYING:线程池中没有线程时自动转换(队列中可能还有任务)
- TIDYING -> TERMINATED:terminated()执行完后就会自动转换
原理
执行流程
-
创建线程池时,线程池中是不会创建线程的。除非我们自己再显示调用
prestartAllCoreThreads()
方法才会去创建核心线程。 -
刚开始线程池中是没有线程的,如果来了任务那么就直接去创建线程处理,如果核心线程处理完任务了,但是线程数量还没有达到核心线程数,此时来了任务也还是会去创建新线程处理,直到线程数量达到了核心线程数。
-
线程数 >= 核心线程数后,再来新任务就会直接放入阻塞队列中,并唤醒等待的线程去处理,当队列中没有任务了那么线程就阻塞。
-
假如队列中放满了,那么才会去创建新的线程去处理任务。
-
如果新创建的线程达到了最大线程数量那么就会触发拒绝策略
我们直接看线程池执行任务的源码:
public void execute(Runnable command) {
if (command == null)
throw new NullPointerException();
int c = ctl.get();
// 首先看当前线程池数量是否小于核心线程数,如果小于则直接调用addWorker()方法 创建新线程
// 这里的addWorker()方法最后一个参数是true
if (workerCountOf(c) < corePoolSize) {
if (addWorker(command, true))
return;
c = ctl.get();
}
// 核心线程数量达到后,就会调用offer()方法入队
if (isRunning(c) && workQueue.offer(command)) {
int recheck = ctl