当前位置: 首页 > 后端技术 > Java

Java线程池配置常见误区

时间:2023-04-01 15:17:42 Java

前言由于线程的创建和销毁对于操作系统来说是比较重量级的操作,所以线程池在各种语言中都有实践。当然,在Java语言中,线程池也是非常重要的一部分。DougLea封装了线程池。我们使用起来非常方便,但是我们也可能因为不了解它的具体实现而对线程池的配置参数产生误解。我们经常在一些技术书籍或者博客上看到,当一个任务被提交到线程池时,线程池的执行逻辑是这样的:核心线程数,如果没有达到,则创建一个线程。如果线程池中运行的线程数已经达到核心线程数,则将任务放入BlockingQueue。如果BlockingQueue满了,线程池会尝试扩充线程数到最大线程池容量。如果当前线程池的线程数已经达到最大线程池容量,则执行拒绝策略拒绝任务提交。流程如图(来自美团科技博客):流程描述没有问题,但如果有些地方考虑不周,容易造成误解,描述的情况过于理想化,如果配置没有考虑运行环境,也会出现一些很奇葩的问题。核心池线程池中线程数小于等于coreSize的部分称为核心池。核心池是线程池的常驻部分。一般不会破坏内螺纹。我们提交的大部分任务应该由核心池处理。线程来执行。线程创建时机的误解对核心池最常见的误解之一是他们没有弄清楚核心池中线程创建的时机。对于这个问题,我认为将10%的责任归咎于DougLea并不过分,因为他在文档中写道“如果正在运行的线程少于corePoolSize,请尝试以给定的命令作为第一个启动一个新线程task”,“running”这个词比较模糊,因为在我们的理解中,running是指当前线程已经被操作系统调度执行,具有操作系统时间分片,或者理解为执行某个任务。基于以上理解,我们很容易想到,如果任务的QPS很低,那么线程池的线程数永远达不到coreSize。即如果我们配置coreSize为1000,其实QPS只有1,单个任务耗时1s,那么corepoolsize会一直为1,即使有流量抖动,corepool也只会扩充为3。因为一个线程每秒执行一个任务,刚好可以处理1QPS,不用新建线程。但是如果你简单的设计一个测试,用jstack打印出线程栈,统计线程池中的线程数,你会发现线程池中的线程数会随着任务的提交逐渐增加,直到达到核心大小。因为核心池的设计初衷是可以作为常驻池来承载日常流量,应该尽快初始化,所以线程池的逻辑是每个任务都会创建一个新的thread在达到coreSize之前,对应的源码是:publicvoidexecute(Runnablecommand){...intc=ctl.get();if(workerCountOf(c)