首页 > 编程语言 >Java线程池核心线程用尽后为何优先排队而不是继续创建线程直至最大线程数?

Java线程池核心线程用尽后为何优先排队而不是继续创建线程直至最大线程数?

时间:2024-05-02 12:23:04浏览次数:30  
标签:Java 用尽 队列 核心 任务 线程 IO CPU

前阵子在v2ex上看到这篇帖子讨论这个问题,有意思的是这个如此基础的问题在Javaer的世界里并没有广泛的共识,下面的回答也是七嘴八舌的,刚好在《Java Performace》上看到对这个问题的解释,尝试总结一下。

原因

书中对线程池的解释基于以下几点前提:

  1. 如果CPU已经跑满,增加线程并不能提高系统吞吐,更多的线程切换开销反而会降低性能
  2. 核心线程用尽之后CPU负载如何线程池并不清楚,这取决于核心线程数的大小以及当前任务的性质(CPU密集还是IO密集)
  3. 线程池不一定要用满所有CPU,有时线程数本来就是一种CPU资源限制的手段

理想情况下线程池中Runnable的线程数应该刚好等于CPU核心数,如果任务都是CPU密集型,那么线程数就等于核心数;如果是IO密集型,那么就需要计算CPU耗时和IO耗时的比例来调整核心线程数。现实中的情况往往更加复杂,任务可能有CPU密集的也有IO密集的,IO密集的耗时比例也不尽相同。调整核心线程数、最大线程数和队列长度来获得理想的系统吞吐和请求耗时这是开发者的责任,线程池提供机制但无法解决这个问题。

线程池的逻辑或者说约定:

  1. 假设需要核心线程数的线程来使CPU达到饱和。如果当前线程数没有达到核心线程数,线程池总是新建线程来执行任务,即使现有线程有空闲的。
  2. 如果现有线程数达到核心线程数而队列未满,将任务推进队列。此时假设CPU资源已经饱和,任务需要等待CPU资源释放,再增加线程只会降低性能。
  3. 如果连队列都已经满了,继续创建线程直至最大线程数。此时新提交的任务依然排队,从队头取一个任务交给新创建的线程。此时线程池认为系统已经过载,创建新线程属于试试能不能抢救一下。
  4. 最大线程数都达到了,再有新任务提交直接调用拒绝策略。

如上,可见设置的关键是核心线程数,核心线程数应该尽量使CPU饱和(或者达到我们期望的负载)但又不会产生过多的上下文切换。考虑到任务的复杂性,这个参数确实只能通过压力测试来得到。

其它的一些模式

ThreadPoolExecutor的配置是非常灵活的,可能通过调整参数使得线程池采取一些别的行为。

令核心线程数等于最大线程数,就可以取得原贴所期望的,可能也是大部分人所期望的,到达最大线程数后再排队。不过核心线程是不会被回收的,如果确实需要回收可以设置allowCoreThreadTimeOut。如果使用无容量限制的队列如 LinkedBlockedingQueue那么行为就和Executors#newFixedThreadPool相同。

令队列长度等于0,最大线程数等于无限(Integer#MAX_VALUE,等效于无限),此时所有任务都会直接提交给线程,没有空闲的就新建,不会有任务排队。此时等效于Executors#newCachedThreadPool

上面两种方式多多少少都有点问题,这也是为什么不建议通过Executors来创建线程池。

标签:Java,用尽,队列,核心,任务,线程,IO,CPU
From: https://www.cnblogs.com/narcissu5/p/18170080

相关文章

  • 一份透心凉的北森java冷面(面经)
    1.对于分布式的理解2.几台机器合作怎么保证高可用3.es打了几个节点4.为什么es快5.es的build和body的区别6.es想进行时间范围搜索,用到什么命令和接口7.es的索引有哪些8.redis为什么搜索快9.在什么地方使用了redis10.将数据直接放到本地内存里更快,为什么用redis11.分布式......
  • java命名规范
    1、java文件名规范a、一个文件中最多只能有一个public类且文件名必须和public类名一致b、文件中可以有多个类,若无public修饰的类,此文件名可以是任意名2、java类与成员命名规范a、类名规范-类名首字母必须大写,使用驼峰命名法b、类修饰符(写在类前边,只有两种)-public-defau......
  • 05.Java 方法详解
    1.方法的定义及调用设计方法的原则:一个方法只完成一个功能,有利于后期的扩展方法的定义:修饰符(可选)返回值类型方法名(参数类型参数名(可选)){方法体return返回值;}2.方法重载重载:就是在一个类中,有相同的函数名称,但形参不通的函数方法的重载规则:方法名称必......
  • Java注解和反射
    元注解:负责注解其他的注解,Java定义了4个标准的meta-annotation类型@Target:用于描述注解的适用范围@Retention:表示需要再什么级别保存该注解信息,用于描述注解的生命周期(source<class<runtime)@Document:该注解江北包含在javadoc中@Inherited:说明子类可以继承父类中的注解自......
  • day29-JavaScript(1)
    1、JavaScript的历史1.1、JavaScript的历史JavaScript因为互联网而生,紧随着浏览器的出现而问世。回顾它的历史,就要从浏览器的历史讲起。1990年底,欧洲核能研究组织(CERN)科学家TimBerners-Lee,在全世界最大的电脑网络——互联网的基础上,发明了万维网(WorldWideWeb),从此可以在网......
  • servlet的生命周期及线程问题
    1.servlet对象的产生,是在第一次使用servlet的时候由Tomcat创建,由Tomcat调用构造方法创建的对象。之后再使用这个servlet就直接使用创建好的对象。servlet在Tomcat服务器中是单实例。2.init()在创建servlet后只调用一次。可以初始化一些公用的函数。通常我们直接使用父类的init就......
  • 04.Java 流程控制
    1.用户交互ScannerScanner对象:获取用户的输入基本语法:Scanners=newScanner(System.in);通过Scanner类的next()和nextLine()方法获取输入的字符串,在读取前一般使用hasNext()和hasNextLine()判断是否还有输入的数据。next():一定要读取到有效字符后才可以结束......
  • Java多线程
    程序,进程,线程程序是指令和数据的有序集合,其本身没有任何运行的含义,是一个静态的概念;进程是执行程序的一次执行过程,是一个动态的概念,是系统资源分配的单位;通常在一个进程中可以包含若干个线程,线程是CPU调度和执行的单位;若是单核cpu,则多线程是模拟出来的,在一个cpu的情况下,在同......
  • 多线程TCP的一些问题
    使用循环堵塞等待客户端连接,连接到一个就开一条线程,当用以下代码,即每次ad重新初始化后其地址作为实参进行线程的创建,结果就是当有新客户端连接,开了新线程时,旧线程看起来会被停止,实际上是因为ad用了地址而不是值作为实参,所以当新连接进来时,ad的值被更改,但地址不变,旧线程所使用的ad......
  • Java IO流之为什么要手动关闭IO流
    目录1IO流关闭1.1问题引入1.2为什么IO流需要手动关闭1.3正确关闭流姿势介绍1.3.1在try中关流而没在finally中关流1.3.2在关闭多个流时将其放在一个try中1.3.3在循环中创建流在循环外关闭1.3.4关闭多个流时没用遵循后定义先释放原则1.3.5jdk7及以上版本推荐try-......