线程池的corePoolSize大小为什么设置为N+1或2N?
根据业务场景来设置线程池中的各个参数。
分析线程池处理的任务是CPU密集型还是IO密集型:
- CPU密集型:corePoolSize = N + 1
- IO密集型:corePoolSize = 2 * N
注意:N+1和2N都是经验值。
出发点:希望CPU一刻不停的工作。
为什么CPU密集型是N+1?
CPU密集型场景下,我们就不再考虑IO了。这时候CPU空闲的原因一般就是正在处理的任务出现错误,暂停了。这个时候如果我们有另一个任务恰好补上来,那CPU就没得歇了。所以是N+1。当然这也是经验值。
网上也有说法是,CPU密集型是N-2N。这个也不难理解,如果我们N个线程都在处理任务,他们发生了错误,每个都有个替补的补上,这就是2N了。
为什么IO密集型是2N?
理想线程数=最大并行数*(CPU计算时间+等待时间)/CPU计算时间
如果理想的认为CPU计算时间等于CPU等待时间,那么理想线程数就是2N了。
当然大部分场景下这是不可能的。一般Web项目中IO时间(CPU等待时间)都远高于CPU计算时间(因此MySQL才会用B+树做索引来减小每查找一层增加的IO)。比如一个简单的数据库查询,CPU计算时间可能只有0.1ms,CPU的IO时间则可能达到2ms,这样一算,就是21N了。所以说2N只是个理想的经验值,在高IO时间开销的场景,可以是10N、20N、30N等等。
在执行任务时动态调整线程池参数
线程池使用面临的核心问题在于:线程池的参数并不好配置。
如果无法找到合理的参数配置,那能否换一个角度考虑:降低修改参数配置的成本。
动态调整核心线程数和最大线程数
在执行任务时,线程池使用方调用setCorePoolSize
方法设置corePoolSize之后,线程池会直接覆盖原来的corePoolSize值。
如果修改后的corePoolSize值小于当前工作线程数的话,说明有多余的工作线程,此时会向当前idle的工作线程发起中断请求以实现回收,多余的工作线程在下次idle的时候也会被回收。
如果修改后的corePoolSize值大于当前工作线程数且任务队列中有待执行任务,则线程池会创建新的工作线程来执行任务队列中的任务。
问题:如果把核心线程数调的过大,那么在业务低峰期的时候是否需要人工调低这个值呢?
回答:并不需要。corePoolSize中有一个参数叫做allowThreadTimeOut,它默认为flase,如果设置为true,就意味着当核心线程空闲时,keepAliveTime(空闲线程存活时间)后也会回收该核心线程。
动态调整阻塞队列(任务队列)的大小
问题:为什么没有提供修改任务队列大小的set方法呢?
回答:因为线程池参数中任务队列的capacity被final修饰了,无法修改。
可以把LinkedBlockingQueue的代码复制一份出来,修改个名字,然后把capacity参数的final修饰符去掉,并提供其对应的get/set方法。然后在程序里面把原来的队列换掉即可。
注意:线程池被创建后如果没有任务过来,里面是不会有线程的。如果需要对线程池进行预热的话,可以调用下面的两个方法,一是启动全部线程(prestartAllCoreThreads),二是启动一个线程(prestartCoreThread)。
注意:核心线程默认不会被回收,如果需要回收,需要调用allowCoreThreadTimeOut方法。