专栏首页阿杜的世界Java线程池容量设置

Java线程池容量设置

本文由杨青同学投稿,总结了他在近期工作中对线程池容量设置的一点经验。原文发于微信公众号:Java线程池容量设置

创建线程池的方式

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

【强制】线程池不允许使用Executors去创建,而是通过ThreadPoolExecutor的方式,这样的处理方式让写的同学更明确线程池的运行规则,规避资源耗尽的风险。 说明:Executors返回的线程池对象弊端如下: 1)FixedThreadPool和SingleThreadPool:允许的请求队列长度为Integer.MAX_VALUE,可能会堆积大量的请求,从而导致OOM。 2)CacheThreadPool和ScheduledThreadPool:允许创建线程数量为Integer.MAX_VALUE,可能会创建大量线程,从而导致OOM。

参数的工作原理

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

  • corePoolSize:
  • maximumPoolSize
  • keepAliveTime
  • unit
  • workQueue
  • threadFactory
  • handler

这几大参数的工作原理如下:

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

具体讲解可以参考清英在infoQ发布的文章:http://www.infoq.com/cn/articles/java-threadPool

线程池容量设置

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

CPU bound

在CS中,CPU密集型任务的执行时间主要取决于CPU的时间。CPU密集型任务的线程数通常设置为CPU核心数+1 ,《Java Concurrency in Practice》(由Doug. Lea等人撰写)给出了如下的解释:

Even computeͲintensive threads occasionally take a page fault or pause for some other reason, so an "extra" runnable thread prevents CPU cycles from going unused when this happens.

即比CPU核心数多出来的一个线程是为了防止线程偶发的page fault或者由于其他原因导致的任务暂停,此时CPU就会处于空闲状态,而在这种情况下多出来的一个线程就可以充分利用CPU的这个空闲时间。

IO bound

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

最佳线程数 = CPU数量 * CPU利用率 *(线程等待时间/线程CPU时间 + 1)

实际最优参数

在实际操作中,由于公式中线程等待时间和线程CPU时间不好估算,以及系统中存在其他的阻塞情况,这样计算出来的最佳线程数往往不是生产环境下的最佳线程数。为了提高精确度,笔者利用有赞内部的压测系统对代码进行压测,通过多次调整计算出来的最佳线程数和观察压测结果(系统负载、接口RT、TPS等指标)来判断线程数是否符合期望。 在压测的过程中发现,当线程数量设置的更合理时TPS更高且接口的RT较低;而线程池设置过大导致TPS下降和RT上涨。由于RT和TPS不太方便直接给出,这里仅展示系统负载这一指标的压测结果。 当线程池设置过大时:

图2 压测时把线程池参数设置得很大

当线程池设置较为合理时:

图3 线程数较为合理


参考资料:

  1. http://www.infoq.com/cn/articles/java-threadPool
  2. https://en.wikipedia.org/wiki/I/O_bound
  3. https://en.wikipedia.org/wiki/CPU-bound
  4. 《Java Concurrency in Practice》

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

我来说两句

0 条评论
登录 后参与评论

相关文章

  • Java线程池监控小结

    最近我们组杨青同学遇到一个使用线程池不当的问题:异步处理的线程池线程将主线程hang住了,分析代码发现是线程池的拒绝策略设置得不合理,设置为CallerRuns...

    阿杜
  • 《七周七并发模型》阅读笔记(一)一、线程与锁——第一天二、线程与锁——第二天三、线程与锁——第三天

    线程与锁模型其实是对底层硬件运行过程的形式化,这种形式化既是该模型最大的优点,也是它最大的缺点。我们借助Java语言来学习线程与锁模型,不过内容也适用于其他语言...

    阿杜
  • ThreadLocal的使用场景

    最近项目中遇到如下的场景:在执行数据迁移时,需要按照用户粒度加锁,因此考虑使用排他锁,迁移工具和业务服务属于两个服务,因此需要使用分布式锁。

    阿杜
  • Java学习笔记(4)——并发基础

    前言 当我们使用计算机时,可以同时做许多事情,例如一边打游戏一边听音乐。这是因为操作系统支持并发任务,从而使得这些工作得以同时进行。 那么提出一个问题:如果我...

    我没有三颗心脏
  • 线程池之ThreadPoolExecutor概述

    Java源码里面都有大量的注释,认真读懂这些注释,就可以把握其七分工作机制了。关于ThreadPoolExecutor的解析,我们就从其类注释开始。

    用户6182664
  • Java线程池详解

    回复:Java全套教程,即可获取Java基础、Java web、Java EE、spring boot等完整学习视频。

    公众号 IT老哥
  • 全网最火Java面试题

    回复:Java全套教程,即可获取Java基础、Java web、Java EE、spring boot等完整学习视频。

    公众号 IT老哥
  • 线程池ThreadPoolExecutor源码分析

    多线程是我们日常工作中很少能接触到的技术,但是面试的时候100%会被问到,万一工作中用到了基本不会,本篇咱们就来深入分析线程池的实现类ThreadPoolExe...

    公众号 IT老哥
  • 【Java多线程-7】阅尽Java千般锁

    根据对同步资源处理策略不同,锁在宏观上分为乐观锁与悲观锁,这只是概念上的一种称呼,Java中并没有具体的实现类叫做乐观锁或者悲观锁。

    云深i不知处
  • 【Java多线程-2】Java线程池详解

    通过前文 线程的创建与使用 ,我们对线程有了一定了解。线程的创建与销毁需要依赖操作系统,其代价是比较高昂的,频繁地创建与销毁线程对系统性能影响较大。

    云深i不知处

扫码关注云+社区

领取腾讯云代金券