线程池可以说是在Java代码中非常常见的技术了,利用线程池技术可以有效的提高系统资源的使用效率,本文将从线程池的类型分类、创建线程池参数、各种类型线程池的使用场景、优秀的设计思路等
Executor 为基础接口,设计初衷是将任务提交和任务执行细节解耦
ExecutorService 则更加完善,不仅提供了service管理功能,也提供了更加全面的提交任务机制
Executors 提供了各种工厂方法,简化使用
下面我们来分析一下5中线程池创建
- newCacheThreadPool()
一种用来处理大量短时间工作任务的线程池,具有几个特点:他会试图缓存线程并重用,当无缓存线程可用时,会创建新的线程,如果闲置时间超过60秒,则被终止并移出缓存,内部使用SynchronousQueue作为工作队列 newFixedThreadPool(int nThreads)
重用指定数目的线程,背后使用无界队列,任何时候最多只有nThreads
个活跃的线程,超过的就会放进阻塞队列中newSingleThreadExecutor()
特点是工作线程只有一个,操作无界队列,保证任务串行执行,单例实现,最多只有一个线程处于活跃状态newSingleThreadScheduleExecutor(int n)
可以调整工作线程数,适用于进行定时或者周期性调度工作newWorkStealingPool(int n)
内部使用fork/join 基于工作窃取模式并行执行
内部工作线程基于AQS实现
线程池中的生命周期还有状态都是通过 ctl 那个变量来体现,一种高效优化的体现
通过高低位的不同,既表示线程池状态,又表示工作线程数目,可以有效的免去操作不同变量同步开销
下图为线程池的状态流转图
线程池实践,我会列举一些常见需要避免的问题
- 避免任务堆积。因为任务都在内存中,堆积可能会导致OOM
- 避免过度扩展线程,即应该设定一下最大活跃线程数量的上限
- 警惕线程数量的不断增涨
- 避免死锁
- 在使用线程池时不应该使用ThreadLocal
线程池的大小选择
- 计算密集型的话 建议 CPU核数 或者核数加一
- I/O操作比较多的话 推荐公式
- 线程数 = CPU 核数 * (1 + 平均等待时间 / 平均工作时间)