线程池的参数设置
- corePoolSize:核心线程数,表示线程池中的常驻线程的个数
- maximumPoolSize:最大线程数,表示线程池中能开辟的最大线程个数
线程池执行任务的类型
执行时间 = 任务总时间 + 上下文切换时间
- CPU密集型任务,比如找出1-1000000中的素数
- 设置线程数为:CPU核心数 + 1
- IO密集型任务,比如文件IO、网络IO
- 设置线程数为:2*CPU核心数
- 混合型任务
- 线程数 = CPU核心数 *( 1 + 线程等待时间 / 线程运行总时间 )
以上是理论,实际工作中如果要确定线程数,最好是压测。
总结
- CPU密集型任务:CPU核心数+1,这样既能充分利用CPU,也不至于有太多的上下文切换成本
- IO型任务:建议压测,或者先用公式计算出一个理论值(理论值通常都比较小)
- 对于核心业务(访问频率高),可以把核心线程数设置为我们压测出来的结果,最大线程数可以等于核心线程数,或者大一点点,比如我们压测时可能会发现500个线程最佳,但是600个线程时也还行,此时600就可以为最大线程数
- 对于非核心业务(访问频率不高),核心线程数可以比较小,避免操作系统去维护不必要的线程,最大线程数可以设置为我们计算或压测出来的结果。
为什么使用线程池
节省开启和销毁线程的消耗
线程池执行任务的具体流程
注意:提交一个Runnable时,不管当前线程池中的线程是否空闲,只要数量小于核心线程数就会创建新线程。
注意:ThreadPoolExecutor相当于是非公平的,比如队列满了之后提交的Runnable可能会比正在排队的Runnable先执行。
- 当线程数 < corePoolSize,创建新线程,执行Runnable。
- Runnable加入到workQueue
- workQueue未满,入队,等待执行
- 当线程数 < maximumPoolSize,创建新线程,执行Runnable。否则拒绝任务。
- Runnable加入到workQueue
线程池中的线程是如何关闭
Thread类提供了一个stop(),但是标记了@Deprecated
建议通过自定义一个变量,或者通过中断 interrupt() 来停掉一个线程
线程池为什么一定得是阻塞队列
线程为了不自然消亡,就会阻塞在获取队列任务,通过这种方法能最终确保,线程池中能保留指定个数的核心线程数。
线程发生异常,会被移出线程池吗
在源码中,当执行任务时出现异常时,最终会执行processWorkerExit(),执行完这个方法后,当前线程也就自然消亡了,但会再新增一个线程,这样就能维持住固定的核心线程数。