Java线程池的最大容量是多少?5亿!

在ThreadPoolExecutor中有一个重要的属性ctl,类型为AtomicInteger,本质是作为一个bitmap来使用。
其中包含两个域,高3位表示线程池的5中状态(Running、ShutDown、Stop、Tidying、Terminated),低29位表示线程池的数量。
因此,理论上,线程池的最大容量为2^29-1=536870911(5亿多)。
但是实际生产中我们通常需要指定一个合理的线程池容量,5亿没有什么现实意义。那么如何合理的设置我们的线程池容量呢?
在《Java并发编程实践》中提到,对于计算密集型的任务,一个具有N个CPU的处理器的系统,通常通过使用N+1个线程的线程池来获得最优的使用率(计算密集型的线程恰好在某时因为发生一个页错误或因为其他原因而暂停,刚好有一个“额外”的线程,可以确保在这种情况下CPU周期不会中断工作)。对于包含了I/O和其他阻塞操作的任务,不是所有的线程都会在所有的时间被调度,因此需要一个更大的池。为了正确的设置线程池的容量,必须估算任务花在等待的时间与用来计算的时间的比率;这个估算值不必十分精确,而且可以通过一些监控工具获得。还可以选择另一种方法来调节线程池的大小,在一个基准负载下,使用几种不同大小的线程池运行我们的应用,并观察CPU利用率水平。
约束线程池容量的因素还有很多:内存、文件句柄、套接字句柄、数据库连接等。计算这类资源池的大小约束很简单:首先累加每一个任务需要的这些资源的总量,然后除以可用的总量。所得的结果是池容量的上限。

基于以上的理论基础,我们的线程池的核心容量(最小容量)应该设置为可用的CPU核数。对于计算密集型的任务,这个容量就足够了,因此线程池的最大容量可以设置为可用的CPU核数+1。设置更大的容量对程序性能而言并没有太大好处,反而不利。因为线程间的上下文切换相对于任务而言会是更大的开销。
但是对于IO密集型的任务(网络IO、磁盘IO),我们就需要更多的线程来提高性能,这是因为会有大量的线程阻塞,导致CPU经常处于空闲状态,无法充分利用计算机的资源。因此我们可以设置线程池的核心容量为可用的CPU核数,最大的线程池容量可以根据一个公式
线程数=N/(1 - 阻塞系数)
这里的阻塞系数来自于《JVM虚拟机并发编程》,阻塞系数=阻塞时间/(阻塞时间+计算时间)。
另外,如果不具备统计阻塞时间的条件,通常设置为2N不会有太大问题。
另外,虽然线程等待时间越久,需要越多的线程。但如果阻塞时间过大,优化的手段未必是无脑的增加线程数量(因为这这样做我们只是减少CPU等待,并非真正提高系统的TPS)。提升系统吞吐量的方式应该是从“短板”入手(如网络、文件、数据库)等,以及使用NIO代替BIO。

不妨添加我的微信公众号,每日精品原创干货不容错过。在这里插入图片描述


版权声明:本文为shanhai3000原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接和本声明。