阿里巴巴Java创建线程池的工作原理及规范分析(二)

如何创建线程池

在Java中,可以通过Executors和ThreadPoolExecutor来创建线程池。四种常见的线程池可以通过Executors快速创建,但是在实际使用中不推荐这种方式,因为这种方式创建的线程池可控性较差,比较推荐的方式是使用ThreadPoolExecutor提供的方法。参考阿里巴巴Java开发规范:

[强制] Executors 不允许创建线程池,但使用 ThreadPoolExecutors。这种处理方式可以让编写者更具体地了解线程池的运行规则,避免资源耗尽的风险。说明:Executors返回的线程池对象的缺点如下: 1)FixedThreadPool和SingleThreadPool:允许的请求队列长度为Integer.MAX_VALUE,可能会堆积大量请求,导致OOM。 2)CacheThreadPool和ScheduledThreadPool:允许创建的线程数为Integer.MAX_VALUE,可能会创建大量线程,导致OOM。参数的工作原理

使用ThreadPoolExecutor创建线程池需要你自己给关键参数:

这些主要参数的工作原理如下:

图1选自infoQ文章聊天并发(三)

详情请参考清影在infoQ上的文章:线程池容量设置

线程池的corePoolSize设置是整个线程池中最关键的参数。如果设置太小,线程池的吞吐量会不足,因为新提交的任务需要排队或由handler处理(取决于拒绝策略);设置太大会耗尽计算机的 CPU 和内存资源。

CPU 限制

在CS中,CPU密集型任务的执行时间主要取决于CPU时间。 CPU 密集型任务的线程数通常设置为 CPU 内核数 + 1,如 Java Concurrency in Practice(Doug.Lea 等人所著)中所述:

即使是计算密集型线程也偶尔会出现页面错误或因其他原因暂停参数估计怎么做,因此“额外”可运行线程可防止 CPU 周期在这种情况下未被使用。

即比CPU核数多1个线程,是为了防止线程偶尔出现page fault或者其他原因导致任务挂起。此时CPU会处于空闲状态,此时又增加了一个线程。您可以充分利用 CPU 的这段空闲时间。

IO 绑定

IO密集型任务是指任务的执行时间主要取决于IO的时间。笔者实习期间遇到的大部分业务场景都是IO绑定的。对于IO绑定,书中给出了如下计算公式:

最优线程数=CPU个数*CPU利用率*(线程等待时间/线程CPU时间+1)实际最优参数

在实际应用中,由于公式中的线程等待时间和线程CPU时间都不容易估算,而且系统中还有其他阻塞情况,这样计算出来的最优线程数往往不是最优的线程数生产环境中的线程。为了提高准确率,作者使用有赞内部压测系统对代码进行测试,通过多次调整计算出的最优线程数并观察压测结果(系统负载、接口RT、TPS等)来判断线程指标)。数字是否符合预期。

在压测过程中发现,线程数设置越合理,TPS越高参数估计怎么做,接口RT越低;而线程池设置过大,导致TPS下降,RT上升。由于RT和TPS不方便直接给出,这里只展示系统负载的压力测试结果。

当线程池设置过大时:

图2 压力测试时线程池参数设置的很大

当线程池设置合理时:

图3 线程数比较合理

原文:

© 版权声明
THE END
喜欢就支持一下吧
点赞0
分享
评论 抢沙发

请登录后发表评论