一. 好处
- 降低资源消耗。通过重复利用已创建的线程降低线程创建和销毁造成的消耗。
- 提高响应速度。当任务到达时,任务可以不需要等到线程创建就能立即执行。
- 提高线程的可管理性。线程是稀缺资源,如果无限制的创建,不仅会消耗系统资源,还会降低系统的稳定性,使用线程池可以进行统一的分配,调优和监控。
二. 线程池的使用
java.util.concurrent.ThreadPoolExecutor类.
2.1 创建线程池
创建一个线程池有以下4个构造函数, 其中前3个都最终调用了最后一个.
2.2 线程池的主要工作机制
- 提交新任务到线程池
- 若核心池未满,创建一个工作线程来执行任务. 若已满,下一步.
- 若工作队列未满, 新提交的任务入队. 若已满,下一步.
- 若线程池未满,创建一个工作线程来执行任务. 若已满,交给饱和策略来处理.
2.3 线程池中线程的状态
- running, 调用了shutdown()后变为shutdown. 当队列为空且线程池为空, tidying态
- running或shutdown, 调用了shutdownNow()后变为stop. 当线程池为空时, tidying态.
- tidying, 调用terminated()后变为terminated.
三. 合理配置线程池
3.1 任务的性质
CPU密集型任务, 尽可能少的线程. 如 Ncpu+1个线程.
IO密集型任务, 由于线程不是一直在执行任务, 可配置尽可能多的线程. 如 2*Ncpu
混合型任务,如果可以拆分为两个执行时间相差不太大的CPU密集型任务和IO密集型任务,分解后的吞吐率要高于串行的吞吐率。如果执行时间相差太大,就没必要分解。
3.2 优先级
PriorityBlockingQueue. 优先级高的任务先执行。 但如果一直有高优先级的任务加入,低优先级的任务可能永远无法执行。
3.3 执行时间
执行时间不同的线程,可以交给不同规模的线程池,也可以使用优先队列,优先执行时间较短的线程。
3.4 数据库连接
依赖数据库连接的任务,因为线程提交SQL之后需要等待数据库返回结果,等待时间越长,CPU空闲时间越多。所以线程数应该设置大一些,才能更好地利用CPU。
3.5 有界队列
建议使用有界队列。有界队列能增加系统的稳定性和预警能力。可以根据需要设置大一些,比如几千。
如果后台线程池里的线程全部要向数据库查询和插入数据,一旦数据库出了问题,导致SQL很慢,线程阻塞,就会大量积压。如果这时设置为无界队列,就有可能撑满内存,导致整个系统不可用。
四、线程池的监控
- taskCount:线程池需要执行的任务数量。
- completedTaskCount:线程池在运行过程中已完成的任务数量。小于或等于taskCount。
- largestPoolSize:线程池曾经创建过的最大线程数量。通过这个数据可以知道线程池是否满过。如等于线程池的最大大小,则表示线程池曾经满了。
- getPoolSize:线程池的线程数量。如果线程池不销毁的话,池里的线程不会自动销毁,所以这个大小只增不+ getActiveCount:获取活动的线程数。
通过扩展线程池进行监控。通过继承线程池并重写线程池的beforeExecute,afterExecute和terminated方法,我们可以在任务执行前,执行后和线程池关闭前干一些事情。如监控任务的平均执行时间,最大执行时间和最小执行时间等。这几个方法在线程池里是空方法。
参考资料