JAVA原生线程池使用建议

求知探索 1年前 ⋅ 764 阅读
线程池的基本概念

线程池(Thread pool)是一种线程使用模式。线程过多会带来调度开销,进而影响缓存局部性和整体性能。而线程池维护着多个线程,等待着监督管理者分配可并发执行的任务。这避免了在处理短时间任务时创建与销毁线程的代价。(来源:维基百科)

Java线程池相关类是在1.5新增的,所属包是rt.jar,包路径是java.util.concurrent,作者是:Doug Lea,从属JSR-166。

Java线程池也遵循线程池的核心设计思路,复用线程,降低线程创建销毁的资源消耗,提供了多种线程池的实现模型,同时也允许开发者定制化开发其他特色线程池

 

java线程池优势

A) 降低资源消耗,提升效率 :通过重复利用已创建的线程,降低线程创建和销毁造成的消耗,从而提高整体的执行效率。

B) 提高线程的管理性:线程是稀缺资源,如果无限制的创建,不仅会消耗系统资源,还会降低系统的稳定性,使用线程池可以进行统一的分配,调优和监控。

C) 可扩展的开发模式:除JVM提供的三种线程池,可以通过实现AbstractExecutorService类定制自己的线程池而支持不同的业务场景。

 

线程池的应用场景

 

01  Tomcat中的线程池

 

Tomcat作为一款优秀的web服务器,为了保证其性能,其内部也有自己的线程池对象:org.apache.tomcat.util.threads.ThreadPoolExecutor 继承自java.util.concurrent.ThreadPoolExecutor。

不同于原生ThreadPoolExecute达到最大线程后,对新增任务立即执行拒绝策略。Tomcat线程池会在此时再次尝试向队列中添加任务,失败后再执行拒绝策略。最大限度保证任务执行。

同时Tomcat内置了TaskQueue作为任务的缓存队列。继承了LinkedBlockingQueue但是重写了offer方法,即当前线程大于核心线程,且提交的任务数大于当前线程数,表示有线程空闲的情况下,返回false,也就是创建线程。主要是为了控制在线程队列无限增长时,无法创建更多的线程而达到最大线程数的问题。

 

02  Sirector 中的线程池

 

Sirector是JD内一个事件处理编排框架,内置ExecutorService对象,负责对任务的分配处理。

初始化的对象是WorkerExecutor, WorkerExecutor继承自ThreadPoolExecutor,扩展了submit方法用于执行通过sirector编排的具体任务。

WorkerExecutor内置了工厂WorkerThreadFactory主要记录了当前线程池的名称、工厂创建的线程数目等。

使用的拒绝策略为RejectedTaskController,继承自RejectedExecutionHandler,处理方法类似于AbortPolicy策略,丢弃任务抛出异常,抛出异常前也打印了一些异常信息,辅助排查问题。

 

03  个人开发中的线程池

 

参考上面几种框架的线程池,可以得出大概几点结论。

如果个人开发中涉及线程池,要先确认任务场景,是I/O密集还是CPU密集任务,从而确定线程池类型。

再通过使用场景,是最大限度保证任务执行,还是为了保证服务性能,来定制自己的执行策略,并且确定选择任务队列以及拒绝策略。拒绝策略可以参考Dubbo中,同时打印线程信息,辅助排查问题。

然后确定是否需要自定义线程工厂,这里建议自定义线程工厂,在创建线程的时候打上标识,和系统线程加以区分。

在根据任务类型,配置上合理的线程池参数。一个属于你的线程池就搭好了!

线程池参数设置

这里个人认为,没有一种万能的参数一定适合所有的线程池使用场景。

但是有通用的思路来寻找适合当前线程池的最佳参数。

1、确定当前任务类型,是CPU密集还是I/O密集型任务。这两者差别很大。CPU密集和CPU核数以及CPU超线程有关。而I/O密集则和服务处理的任务有很大关联。

2、如果使用一些已有的技术框架中的线程池。初期建议以默认参数为佳,如Tomcat默认范围25-200,JSF默认cached线程池20-200。

3、在服务稳定之后的性能调优。需要对服务进行多次高保真压测,期间不断控制、调整线程池参数,这样尽可能得到当前服务最优的线程池参数。

4、只有最合适的、没有一定不变的,随着业务不断迭代,每隔一段时间对服务进行压测,通过结果调整相应的参数。

 

线程池使用过程中的建议

1 、当提交一个任务到线程池时,若线程数量 < corePoolSize,线程池会创建一个新线程放入workers(一个HashSet)中执行任务, 即使其他空闲的基本线程能够执行新任务也还是会创建新线程,直至达到corePoolSize。

2 、默认最初的线程池启动的时候是不初始化线程的,通过调用 prestartAllCoreThreads 方法,可以初始化所有核心线程。

3 、Worker中处理task如果抛出异常,这个work thread不会继续执行任务,但是会创建新的线程, 新线程可以运行其他task。

4 、最好不要使用Executors创建新线程池,因为Executors提供的很多方法,没有指定实际核心及最大线程池参数,容易发生OOM,推荐自己创建相应的线程池,适合自己的才是最好的,同时线程池中有很多钩子方法可以用来定制特色功能


全部评论: 0

    我有话说: