工作方式,默认 ThreadPoolExecutor,不初始化核心线程,每当一个任务到来,才创建一个核心线程执行;当核心线程数占满了后不会立即扩容,新任务会先堆积到阻塞队列;当阻塞队列占满了后才会扩容线程,直到到达最大线程数;最后线程数达到最大线程数,且队列占满,新任务会执行拒绝策略,keepAliveTime 是指大于核心线程数的线程在等待一定时间后会被销毁;
改变工作方式,声明线程池后立即调用 prestartAllCoreThreads(),可以来启动所有核心线程,调用 allowCoreThreadTimeOut(true) ,可以让线程池在空闲时同样回收核心线程;
execute(Runnable run),无返回值,无法得知任务执行结果;submit(Runnable run),返回一个 Future 类,通过其 get 方法阻塞获取任务执行结果,并且 get 可以指定一个阻塞时间,避免长时间阻塞等待;
ThreadPoolExecutor 的一个问题是,扩容线程往往不够及时,在阻塞队列占满后才会扩容线程,为此可以做一个改变,重写阻塞队列,让其最开始就返回队列满的假象,使得先扩容线程,同时要改写拒绝策略,让无法调度的任务插入到队列中,tomcat 线程池就实现了类似的效果,先扩容线程,后加入阻塞队列;
线程数的计算方式,接口 TPS 100,接口响应时间 100ms,表示 1s 内会有 100 个请求,每个请求耗时 0.1 s,串行需要 10s,因此需要 100*0.1 = 10 个线程,才能保证接口 TPS 100,通常要考虑的一个问题是,当接口响应时间变慢时,线程数和 TPS 的变化;
对于 IO 绑定任务,执行时间长,cpu 利用率不高,要优先创建更多线程数,而不是建立大的阻塞队列;对于 cpu 绑定任务,线程数可定义为 cpu 核数,或 cpu 核数 * 2,因为任务占 cpu 时间,过多线程反而需要 cpu 频繁切换上下文,开销大;
// 获得硬件的 cpu 个数 Runtime.getRuntime().availableProcessors()线程池不要盲目复用,不同类型任务,执行时间长短不一的任务需要单独创建线程池,但也不能每次都创建新的线程池,注意 Java 8 的 parallel stream,背后是共享同一个 ForkJoinPool;
一般都需要使用有界队列和可控的线程数,队列可根据需求设置大一点,但要考虑队列过大,一直不会满,最大线程数就没意义了,或者最后才扩容线程,滞后了;
通常使用 shutdown 来关闭线程池,如果正在执行的任务,不需要执行完,可以调用 shutdownNow,正常的 ThreadPoolExecutor 并不会被回收,因为核心线程会一直存在,不会销毁;
shutdown(),会将线程池的状态置为 shutdown,中断所有没任务调度的线程,然后尝试停止所有的正在执行,或任务暂停的线程,并返回等待执行任务的列表;
shutdownNow(),是遍历线程池中的工作线程,然后逐个调用线程的 interrupt,来中断线程,因为如果是无法响应 interupt 的线程,可能永远无法终止;
shutdown 和 shutdownNow,如果两者调用了其一,isShutdown() 方法就会返回 true,当所有的线程都结束后,才表示线程池关闭成功,这时 isTerminaed() 就会返回 true;
继承线程池,重写其 beforeExecute,afterExecute,terminated 方法,就可以在任务执行前,执行后,和线程池关闭前,织入一些逻辑,如监控任务的平均执行时间,最大执行时间,和最小执行时间等;
源码分析,参考:https://www.cnblogs.com/fixzd/p/9253203.html;