18讲如何设置线程池大小 - 图118讲如何设置线程池⼤⼩

你好,我是刘超。

18讲如何设置线程池大小 - 图2还记得我在16讲中说过“线程池的线程数量设置过多会导致线程竞争激烈”吗?今天再补⼀句,如果线程数量设置过少的话,还会导致系统⽆法充分利⽤计算机资源。那么如何设置才不会影响系统性能呢?

其实线程池的设置是有⽅法的,不是凭借简单的估算来决定的。今天我们就来看看究竟有哪些计算⽅法可以复⽤,线程池中各个参数之间⼜存在怎样的关系。

线程池原理
开始优化之前,我们先来看看线程池的实现原理,有助于你更好地理解后⾯的内容。

在HotSpot VM的线程模型中,Java线程被⼀对⼀映射为内核线程。Java在使⽤线程执⾏程序时,需要创建⼀个内核线程;当该Java线程被终⽌时,这个内核线程也会被回收。因此Java线程的创建与销毁将会消耗⼀定的计算机资源,从⽽增加系统的性能开销。

除此之外,⼤量创建线程同样会给系统带来性能问题,因为内存和CPU资源都将被线程抢占,如果处理不当,就会发⽣内存溢出、CPU使⽤率超负荷等问题。

为了解决上述两类问题,Java提供了线程池概念,对于频繁创建线程的业务场景,线程池可以创建固定的线程数量,并且在操作系统底层,轻量级进程将会把这些线程映射到内核。

线程池可以提⾼线程复⽤,⼜可以固定最⼤线程使⽤量,防⽌⽆限制地创建线程。当程序提交⼀个任务需要⼀个线程时,会去线程池中查找是否有空闲的线程,若有,则直接使⽤线程池中的线程⼯作,若没有,会去判断当前已创建的线程数量是否超过最⼤线程数量,如未超过,则创建新线程,如已超过,则进⾏排队等待或者直接抛出异常。

线程池框架Executor
Java最开始提供了ThreadPool实现了线程池,为了更好地实现⽤户级的线程调度,更有效地帮助开发⼈员进⾏多线程开
发,Java提供了⼀套Executor框架。

这个框架中包括了ScheduledThreadPoolExecutor和ThreadPoolExecutor两个核⼼线程池。前者是⽤来定时执⾏任务,后者是
⽤来执⾏被提交的任务。鉴于这两个线程池的核⼼原理是⼀样的,下⾯我们就重点看看ThreadPoolExecutor类是如何实现线程池的。

Executors实现了以下四种类型的ThreadPoolExecutor:
18讲如何设置线程池大小 - 图3
Executors利⽤⼯⼚模式实现的四种线程池,我们在使⽤的时候需要结合⽣产环境下的实际场景。不过我不太推荐使⽤它们, 因为选择使⽤Executors提供的⼯⼚类,将会忽略很多线程池的参数设置,⼯⼚类⼀旦选择设置默认参数,就很容易导致⽆法调优参数设置,从⽽产⽣性能问题或者资源浪费。

这⾥我建议你使⽤ThreadPoolExecutor⾃我定制⼀套线程池。进⼊四种⼯⼚类后,我们可以发现除了
newScheduledThreadPool类,其它类均使⽤了ThreadPoolExecutor类进⾏实现,你可以通过以下代码简单看下该⽅法:



public ThreadPoolExecutor(int corePoolSize,//线程池的核⼼线程数量
int maximumPoolSize,//线程池的最⼤线程数
long keepAliveTime,//当线程数⼤于核⼼线程数时,多余的空闲线程存活的最⻓时间TimeUnit unit,//时间单位
BlockingQueue workQueue,//任务队列,⽤来储存等待执⾏任务的队列ThreadFactory threadFactory,//线程⼯⼚,⽤来创建线程,⼀般默认即可RejectedExecutionHandler handler) //拒绝策略,当提交的任务过多⽽不能及时处理时,我们可以

18讲如何设置线程池大小 - 图4

我们还可以通过下⾯这张图来了解下线程池中各个参数的相互关系:

18讲如何设置线程池大小 - 图5

通过上图,我们发现线程池有两个线程数的设置,⼀个为核⼼线程数,⼀个为最⼤线程数。在创建完线程池之后,默认情况
下,线程池中并没有任何线程,等到有任务来才创建线程去执⾏任务。

但有⼀种情况排除在外,就是调⽤prestartAllCoreThreads()或者prestartCoreThread()⽅法的话,可以提前创建等于核⼼线程数的线程数量,这种⽅式被称为预热,在抢购系统中就经常被⽤到。

当创建的线程数等于 corePoolSize 时,提交的任务会被加⼊到设置的阻塞队列中。当队列满了,会创建线程执⾏任务,直到线程池中的数量等于maximumPoolSize。

当线程数量已经等于maximumPoolSize时, 新提交的任务⽆法加⼊到等待队列,也⽆法创建⾮核⼼线程直接执⾏,我们⼜没有为线程池设置拒绝策略,这时线程池就会抛出RejectedExecutionException异常,即线程池拒绝接受这个任务。

当线程池中创建的线程数量超过设置的corePoolSize,在某些线程处理完任务后,如果等待keepAliveTime时间后仍然没有新的任务分配给它,那么这个线程将会被回收。线程池回收线程时,会对所谓的“核⼼线程”和“⾮核⼼线程”⼀视同仁,直到线程池中线程的数量等于设置的corePoolSize参数,回收过程才会停⽌。

即使是corePoolSize线程,在⼀些⾮核⼼业务的线程池中,如果⻓时间地占⽤线程数量,也可能会影响到核⼼业务的线程池, 这个时候就需要把没有分配任务的线程回收掉。

我们可以通过allowCoreThreadTimeOut设置项要求线程池:将包括“核⼼线程”在内的,没有任务分配的所有线程,在等待
keepAliveTime时间后全部回收掉。

我们可以通过下⾯这张图来了解下线程池的线程分配流程:
18讲如何设置线程池大小 - 图6

计算线程数量
了解完线程池的实现原理和框架,我们就可以动⼿实践优化线程池的设置了。

我们知道,环境具有多变性,设置⼀个绝对精准的线程数其实是不⼤可能的,但我们可以通过⼀些实际操作因素来计算出⼀个合理的线程数,避免由于线程池设置不合理⽽导致的性能问题。下⾯我们就来看看具体的计算⽅法。

⼀般多线程执⾏的任务类型可以分为CPU密集型和I/O密集型,根据不同的任务类型,我们计算线程数的⽅法也不⼀样。

CPU密集型任务:这种任务消耗的主要是CPU资源,可以将线程数设置为N(CPU核⼼数)+1,⽐CPU核⼼数多出来的⼀个线程是为了防⽌线程偶发的缺⻚中断,或者其它原因导致的任务暂停⽽带来的影响。⼀旦任务暂停,CPU就会处于空闲状
态,⽽在这种情况下多出来的⼀个线程就可以充分利⽤CPU的空闲时间。

下⾯我们⽤⼀个例⼦来验证下这个⽅法的可⾏性,通过观察CPU密集型任务在不同线程数下的性能情况就可以得出结果,你可以点击Github下载到本地运⾏测试:

public class CPUTypeTest implements Runnable {

//整体执⾏时间,包括在队列中等待的时间List wholeTimeList;
//真正执⾏时间
List runTimeList;

private long initStartTime = 0;

/**

  • 构造函数
  • @param runTimeList
  • @param wholeTimeList

*/
public CPUTypeTest(List runTimeList, List wholeTimeList) { initStartTime = System.currentTimeMillis();
this.runTimeList = runTimeList; this.wholeTimeList = wholeTimeList;
}

/**

  • 判断素数
  • @param number
  • @return

*/
public boolean isPrime(final int number) { if (number <= 1)
return false;

for (int i = 2; i <= Math.sqrt(number); i++) { if (number % i == 0)
return false;
}
return true;
}

/**

  • 計算素数
  • @param number
  • @return

*/
public int countPrimes(final int lower, final int upper) { int total = 0;
for (int i = lower; i <= upper; i++) { if (isPrime(i))
total++;
}
return total;
}

public void run() {
long start = System.currentTimeMillis(); countPrimes(1, 1000000);
long end = System.currentTimeMillis();

long wholeTime = end - initStartTime; long runTime = end - start; wholeTimeList.add(wholeTime); runTimeList.add(runTime);
System.out.println(“单个线程花费时间:” + (end - start));
}
}

测试代码在4核 intel i5 CPU机器上的运⾏时间变化如下:

18讲如何设置线程池大小 - 图7

综上可知:当线程数量太⼩,同⼀时间⼤量请求将被阻塞在线程队列中排队等待执⾏线程,此时CPU没有得到充分利⽤;当
线程数量太⼤,被创建的执⾏线程同时在争取CPU资源,⼜会导致⼤量的上下⽂切换,从⽽增加线程的执⾏时间,影响了整体执⾏效率。通过测试可知,4~6个线程数是最合适的。

I/O密集型任务:这种任务应⽤起来,系统会⽤⼤部分的时间来处理I/O交互,⽽线程在处理I/O的时间段内不会占⽤CPU来处理,这时就可以将CPU交出给其它线程使⽤。因此在I/O密集型任务的应⽤中,我们可以多配置⼀些线程,具体的计算⽅法是
2N。

这⾥我们还是通过⼀个例⼦来验证下这个公式是否可以标准化:

public class IOTypeTest implements Runnable {

//整体执⾏时间,包括在队列中等待的时间Vector wholeTimeList;
//真正执⾏时间
Vector runTimeList;

private long initStartTime = 0;

/**

  • 构造函数
  • @param runTimeList
  • @param wholeTimeList

*/
public IOTypeTest(Vector runTimeList, Vector wholeTimeList) { initStartTime = System.currentTimeMillis();

this.runTimeList = runTimeList; this.wholeTimeList = wholeTimeList;
}

/*
IO操作

  • @param number
  • @return
  • @throws IOException

*/
public void readAndWrite() throws IOException { File sourceFile = new File(“D:/test.txt”);
//创建输⼊流
BufferedReader input = new BufferedReader(new FileReader(sourceFile));
//读取源⽂件,写⼊到新的⽂件String line = null;
while((line = input.readLine()) != null){
//System.out.println(line);
}
//关闭输⼊输出流input.close();
}

public void run() {
long start = System.currentTimeMillis(); try {
readAndWrite();
} catch (IOException e) {
// TODO Auto-generated catch block e.printStackTrace();
}
long end = System.currentTimeMillis();

long wholeTime = end - initStartTime; long runTime = end - start; wholeTimeList.add(wholeTime); runTimeList.add(runTime);
System.out.println(“单个线程花费时间:” + (end - start));
}
}

备注:由于测试代码读取2MB⼤⼩的⽂件,涉及到⼤内存,所以在运⾏之前,我们需要调整JVM的堆内存空间:-Xms4g -

Xmx4g,避免发⽣频繁的FullGC,影响测试结果。
18讲如何设置线程池大小 - 图8
通过测试结果,我们可以看到每个线程所花费的时间。当线程数量在8时,线程平均执⾏时间是最佳的,这个线程数量和我们的计算公式所得的结果就差不多。

看完以上两种情况下的线程计算⽅法,你可能还想说,在平常的应⽤场景中,我们常常遇不到这两种极端情况,那么碰上⼀些常规的业务操作,⽐如,通过⼀个线程池实现向⽤户定时推送消息的业务,我们⼜该如何设置线程池的数量呢?

此时我们可以参考以下公式来计算线程数:

线程数=N(CPU核数)*(1+WT(线程等待时间)/ST(线程时间运⾏时间))

我们可以通过JDK⾃带的⼯具VisualVM来查看WT/ST⽐例,以下例⼦是基于运⾏纯CPU运算的例⼦,我们可以看到:

WT(线程等待时间)= 36788ms [线程运⾏总时间] - 36788ms[ST(线程时间运⾏时间)]= 0
线程数=N(CPU核数)*(1+ 0 [WT(线程等待时间)]/36788ms[ST(线程时间运⾏时间)])= N(CPU核数)

这跟我们之前通过CPU密集型的计算公式N+1所得出的结果差不多。

18讲如何设置线程池大小 - 图9

综合来看,我们可以根据⾃⼰的业务场景,从“N+1”和“2N”两个公式中选出⼀个适合的,计算出⼀个⼤概的线程数量,之后通
过实际压测,逐渐往“增⼤线程数量”和“减⼩线程数量”这两个⽅向调整,然后观察整体的处理时间变化,最终确定⼀个具体的线程数量。

总结
今天我们主要学习了线程池的实现原理,Java线程的创建和消耗会给系统带来性能开销,因此Java提供了线程池来复⽤线程,提⾼程序的并发效率。

Java通过⽤户线程与内核线程结合的1:1线程模型来实现,Java将线程的调度和管理设置在了⽤户态,提供了⼀套Executor框架来帮助开发⼈员提⾼效率。Executor框架不仅包括了线程池的管理,还提供了线程⼯⼚、队列以及拒绝策略等,可以说
Executor框架为并发编程提供了⼀个完善的架构体系。

在不同的业务场景以及不同配置的部署机器中,线程池的线程数量设置是不⼀样的。其设置不宜过⼤,也不宜过⼩,要根据具体情况,计算出⼀个⼤概的数值,再通过实际的性能测试,计算出⼀个合理的线程数量。

我们要提⾼线程池的处理能⼒,⼀定要先保证⼀个合理的线程数量,也就是保证CPU处理线程的最⼤化。在此前提下,我们 再增⼤线程池队列,通过队列将来不及处理的线程缓存起来。在设置缓存队列时,我们要尽量使⽤⼀个有界队列,以防因队列过⼤⽽导致的内存溢出问题。

思考题
在程序中,除了并⾏段代码,还有串⾏段代码。那么当程序同时存在串⾏和并⾏操作时,优化并⾏操作是不是优化系统的关键呢?

期待在留⾔区看到你的⻅解。也欢迎你点击“请朋友读”,把今天的内容分享给身边的朋友,邀请他⼀起讨论。
18讲如何设置线程池大小 - 图10

  1. 精选留⾔ <br />![](https://cdn.nlark.com/yuque/0/2022/png/1852637/1646315887942-a2706bbe-c45a-42df-b5f3-ce27bac06b76.png#)趙衍<br />⽼师好!关于线程池我有⼀个问题⼀直不明⽩,在线程池达到了核⼼线程数,等待队列没满的这段时间,新的任务会被加⼊到等待队列。⽽当等待队列满了之后,最⼤线程数没满的这段时间,线程池会为新的任务直接创建线程。那岂不是说,我后来的任务反⽽⽐先到的任务更早被分配到线程的资源?这是不是有点不太合理呢?<br />2019-06-30 11:32<br />作者回复<br />对的,如果队列满了,就会新增线程来执⾏任务,如果已经是最⼤线程数量,则会执⾏拒绝策略。

这⾥不应该说不合理,⽽是不公平。可以深⼊源码查看具体的实现。
2019-07-02 09:57

18讲如何设置线程池大小 - 图11QQ怪
⽼师,我想问下⽣产环境情况下很少碰到单个java进程在⼀台主机中运⾏,⼤部分肯定是多个进程同时运⾏,不如docker技术
,都是共享同⼀套硬件,那这套计算⽅程式是不是不适⽤了?
2019-06-29 12:10
作者回复
适⽤的,多个进程⼤部分时间不⼀定是重合运⾏的。但具体情况需要具体定,所以最终还是以压测调出来的线程数为准。
2019-06-30 11:28

18讲如何设置线程池大小 - 图12nico
⽼师,请教个问题,⽣产环境有些应⽤是混部的,即⼀个虚拟机上跑很多个java程序,这个时候估算⼀个程序中的线程池的线程数,是不是就不合理了?这个要怎么估算合理的线程池配置?还有就是即使是单实例部署,cpu资源是机器内共⽤的,不可能只分配给java线程,这个要怎么考虑?
2019-06-29 09:25
作者回复
我们先考虑单个环境,再去调优复杂环境。⼤多情况下,重要的服务会单独部署,尽量减少重要业务的相互影响。如果是核⼼业务冗余在了⼀个服务上,建议拆分之后分别部署。

⾮核⼼业务,很多业务处理可能是在不同时间点,彼此相互不影响,所以不⽤过多考虑混合部署服务的情况。
2019-06-30 10:59

18讲如何设置线程池大小 - 图13明翼
⽼师早点发这个课就好了,先回答问题:程序的总体时间是由所有部分加起来时间决定的,串⾏如果很慢就会严重影响性能,优化是从性能最差的地⽅开始的。

请教问题:
1)按照⽼师的图,如果线程没超过核⼼线程,就创建,超过则加⼊到队列,队列满⼜没达到最⼤线程则创建⾮核⼼线程,那么创建好的线程是直接执⾏最近来的任务那,还是从队列的头部取⼀个执⾏。
2)第⼆个问题,如果线程池的线程数的在核⼼线程数量之内,A线程执⾏刚执⾏完任务,这时候来了个新来的任务a,那么这个A线程继续执⾏这个新来任务a,还是其他线程执⾏这个线程那,这⾥⾯有什么分配策略
2019-07-02 09:03
作者回复
1、如果队列满了,这个现成的任务会创建⾮核⼼线程,也就是不会先运⾏队列中的任务。
2、新来的任务a会通过创建新的线程来运⾏,只要线程数量⼩于核⼼线程数,新来的任务都会通过创建新的线程来运⾏。直到等于核⼼线程数,任务将会放到阻塞队列中,通过循环拿到阻塞队列中的任务执⾏。
2019-07-02 11:13

18讲如何设置线程池大小 - 图14Liam
请教⽼师⼀个问题:

对于应⽤⽽⾔,可能有多种类型的任务要执⾏,我们是分别创建不同的线程池还是创建⼀个统⼀的线程池来控制资源的使⽤呢

  1. 如果⽤⼀个统⼀的线程池,担⼼io任务占有太多线程导致其他任务没有⾜够的线程消费

  2. 如果⽤多个线程池,这个资源怎么管理,会不会导致整个应⽤的线程数量过多,引起太多上下⽂切换从⽽导致开销过⼤

2019-06-29 08:40
作者回复
分别创建。

如果过分彼此相互影响,建议拆开服务,分别部署。
2019-06-30 10:49

18讲如何设置线程池大小 - 图15张学磊
⽼师,线程池流程图提交任务后的第⼀个节点应该是线程数是否⼤于核⼼线程数,如果是再判断队列是否已满,否则直接创建新线程。

思考题,个⼈觉得线性执⾏的代码会成为影响性能的关键,应尽量减少执⾏时间,⽐如减少锁持有时间,这样才能达到最⼤程度的并发。
2019-06-29 08:16
作者回复
感谢学磊童鞋的提醒,已修正。

答案正确!
2019-06-30 10:47

18讲如何设置线程池大小 - 图16再续啸傲
⽼师,我在本地测试的时候,随着核⼼线程数的增加,表现出线程平均执⾏时间增⻓很快,但是线程整体实⾏时间却⼀直在下降。这是否说明任务在等待队列的等待时间要远⼤于线程之间上下⽂切换所花费的时间?
如果要将tomcat线程数设置成两位数,是否对服务TPS有较⼤影响。当万级请求进来的时候正常请求被阻塞,导致前端⻚⾯显示请求超时?
2019-07-09 11:50
作者回复
是的,根据当前机器的CPU数量和执⾏的具体业务来定,如果是纯CPU执⾏的任务,⼀般是跟CPU数量的核⼼线程数量会最⼤
效率利⽤CPU,⽽如果是I/O型业务,本身处理I/O的时间是不占⽤CPU的,可以适当将核⼼线程数调⼤⼀些。如果核⼼线程数远远⼤于CPU的核数,整体执⾏时间也会增加。

Tomcat线程数需要根据⾃⼰的业务和机器的环境来定,正常业务⼀般⽐机器的CPU核数⼤⼀些即可。当万级并发请求时,如果是不保持⻓连接的情况下,可以适当调⼤acceptCount参数。当然,业务处理时间过⻓,在⼤并发量出现,由于tomcat⽆法及时处理,会导致504或其他错误。
2019-07-12 09:32

18讲如何设置线程池大小 - 图17Jxin
思考题:⼀般会选择优化串⾏,毕竟⼤部分并⾏的异步操作都⽆需拿返回值,⼀般是做⼀下⾮强⼀致性的同步,收尾操作。但也不排除特例,⽐如⽣产消费模式,异步消费如果是整个调⽤链的瓶颈点,那么优化异步的消费代码就合理。所以优化串⾏代码还是并⾏代码就是看情况的选择项。
额外请教,如果发包中断进程,哪怕有做优雅关闭,异步线程调⽤好像都是拒绝的,也就是会直接抛异常。那么异常的回滚操作是不是就不能有异步操作?mq调⽤好像也是抛异常的。(实际⼯作出现过脏数据,猜测是这样,⾃⼰⼜没测试,望⽼师指明

2019-07-01 09:42
作者回复
我们发包⼀般是通过⽹关将请求拦截,将请求转发到其他服务,并排除当前服务没有业务⽇志运⾏,再进⾏部署,这种可以排除你说的部署中断造成的异常问题。
2019-07-02 10:43

18讲如何设置线程池大小 - 图18Jxin
1.能理解io操作不占⽤cpu计算,但是io线程依旧不会让渡cpu资源的(执⾏时间⽚)。所以io线程池独⽴和调整io线程数只是因为它耗时不确定,⼀般耗时较⻓的特性。
2.综上所述,那么线程池线程数基本就20-30的样⼦(⽽且这个值怎么感觉是多个线程池的线程总数呢)。那么tomcat线程池默认好像200条吧,dubbo线程池我们设置1000条线程,这是否就不合理了?(线程这块太⽔,麻烦⽼师解惑下)
2019-07-01 09:30
作者回复
我们⼀般会调整tomcat的线程数量的,线上环境的tomcat线程数量我们⼀般是在16核CPU的环境下为20左右。如果有万级的并发过来,100以及1000的线程数量,可能会有问题。
2019-07-02 10:26

18讲如何设置线程池大小 - 图19夏天39度
超哥,如何设置有界队列的⼤⼩啊,设置⼤了容易oom,⼩了吞吐量⼜不够,有什么什么计算公式啊
2019-07-01 08:58
作者回复
没有相应的计算公式,不过我们可以通过压测来设置⼀个合理的⼤⼩。
2019-07-02 10:04

17702158422
⽼师请问下⼀般服务器下所有进程的线程⾄少100多个了,包括JAVA应⽤下也不⽌线程池的线程,还有垃圾回收线程,TOMC
AT内置线程等等,这些线程都跑在⼏个核上,他们也存在上下⽂切换呀,改如何计算⾃定义线程池的数量呢
2019-06-30 15:45
作者回复
以上计算线程池⼤⼩都是基于⼀个⽐较理想状态下计算的。你说的这些线上环境只是⼀个微⼩的影响因素,实际在运⾏时,同时与线程池竞争资源的线程不会对这个公式造成⾮常⼤的影响。这些线程对CPU的使⽤率⼀般不会超过20%,系统负载也在1.
0以下。

如果线上环境已经对线程池的⼤⼩产⽣了⼲扰,也就是说同时存在多线程池同时运⾏,且竞争CPU资源⾮常激烈,这个时候应该模拟线上环境,通过性能测试来调优线程池的线程数量。
2019-07-02 10:23

18讲如何设置线程池大小 - 图20周星星
⽼师,请问⼀下,图中是看线程执⾏时间还是整体花费时间,我测试CPU密集型任务在我电脑中是整体时间下降、执⾏⾏时间

上上升的。

线程数 平均线程整体花费时间 平均线程执⾏花费时间
2 34513 1354
4 19912 1494
8 14138 2028
16 10632 2855
32 9928 4683

2019-06-30 12:57
作者回复
看平均线程整体花费时间,继续增⼤线程数量,则平均线程整体花费时间会开始上升。
2019-07-02 10:03

18讲如何设置线程池大小 - 图21⻜翔
话说N+1和2N 是指的核⼼线程数嘛? 那队列和最⼤线程数怎么设置呀
2019-06-30 03:08
作者回复
在⼀些⾮核⼼业务,我们可以将核⼼线程数设置⼩⼀些,最⼤线程数量设置为计算线程数量。在⼀些核⼼业务中,两者可以设置⼀样。阻塞队列可以根据具体业务场景设置,如果线程处理业务⾮常迅速,我们可以考虑将阻塞队列设置⼤⼀些,处理的请求吞吐量会⼤些;如果线程处理业务⾮常耗时,阻塞队列设置⼩些,防⽌请求在阻塞队列中等待过⻓时间⽽导致请求已超时。
2019-06-30 12:07

18讲如何设置线程池大小 - 图22WL
有三个问题请教⼀下⽼师:

  1. 守护线程也会映射到CPU的线程吗, 我还是没太理解在计算线程数量的时候为啥不考虑守护线程呢, 是因为守护线程运⾏状态是要么⼀直运⾏, 要么⼀直等待这样不会变化的情况所以不考虑吗? 为啥守护线程的状态⼀直不变化呢, 它们不会出让CPU吗?
  2. ⽂章中讲的主要是核⼼线程的数量如何配置, 最⼤线程数量和阻塞队列⻓度应该如何确定, 是随便配⼀下就⾏影响不⼤吗, 还是也有什么指导原则?
  3. 想问⼀下⽼师在visualVM中的线程查看的视窗中线程状态等待, 休眠, 驻留, 监视这⼏个状态都是啥意思, 是啥情况下会变成这

⼏个状态.

2019-06-29 16:37
作者回复
1、守护线程也是JVM创建的线程,这块我没有具体看到源码。我们⼀般不会使⽤守护线程处理业务,我们这⾥讨论的是处理业务的线程数量。
2、在⼀些⾮核⼼业务,我们可以将核⼼线程数设置⼩⼀些,最⼤线程数量设置为计算线程数量。在⼀些核⼼业务中,两者可以设置⼀样。阻塞队列可以根据具体业务场景设置,如果线程处理业务⾮常迅速,我们可以考虑将阻塞队列设置⼤⼀些,处理的请求吞吐量会⼤些;如果线程处理业务⾮常耗时,阻塞队列设置⼩些,防⽌请求在阻塞队列中等待过⻓时间⽽导致请求已超时。

3、休眠对应的sleep操作,等待对应的wait,驻留对应的线程池⾥的空闲线程,监视对应的synchronized阻塞
2019-06-30 12:06

18讲如何设置线程池大小 - 图23杨俊
⽼是我可以这样理解吗,阻塞队列满了之后创建的是⾮核⼼线程⽽不是核⼼线程,⾮核⼼线程执⾏完可能空闲⼀段时间就会被销毁,除⾮达到最⼤线程数,否则当阻塞时候有任务就会创建⾮核⼼线程,那是不是有可能这个⾮核⼼线程会⽐阻塞队列⾥⾯的先执⾏任务呢
2019-06-29 16:16
作者回复
最后这个问题我没有太理解。⾮核⼼线程执⾏的任务可能是⾮阻塞队列的任务?
2019-06-30 11:49

18讲如何设置线程池大小 - 图24Nu11PointerEx
刘⽼师你好,我想问下测试结果的可视化图表是如何制作的?

2019-06-29 14:29
作者回复
通过excel制作的
2019-06-30 11:29

18讲如何设置线程池大小 - 图25colin
线程分配流程图的第⼀个分⽀应该是⼤于吧

线程池我的理解就是,经量使⽤核⼼线程。能等到核⼼线程的就不创建⾮核⼼线程。有⻓时间不⼯作的线程,那么就回收它,保证线程数量在核⼼线程数量内
2019-06-29 13:41
作者回复
是的,已修正。理解到位
2019-06-30 11:30

18讲如何设置线程池大小 - 图26许童童
优化并⾏操作是不是优化系统的关键呢? 可以参考阿姆达尔定律
S=1/(1-a+a/n)
总之,优化并⾏操作带来的收益是有上限的。
2019-06-29 11:48
作者回复
赞,Amdahl’s定律指出优化串⾏是优化系统性能的关键,我们应该从算法⼊⼿,减少程序中串⾏的部分,⽽不是增加线程数来提⾼系统的并发处理能⼒。
2019-06-30 11:25

18讲如何设置线程池大小 - 图27听⾬
⽼师,程序很快就跑完了,visualvm还没打开程序呢,我看你的图监视的是已终⽌的程序,那怎么显示已终⽌程序的信息的呢
2019-06-29 11:00
作者回复
⽤过debug模式,在main函数第⼀条语句设置⼀个断点,进⼊断点后,再去VisualVM操作CPU采样,之后再运⾏程序。
2019-06-30 11:14

18讲如何设置线程池大小 - 图28听⾬
⽼师,您说的java线程和内核线程⼀⼀对应。那我想的是:
1.不管java线程是否阻塞,只要没有销毁,对应的内核线程就⼀直存在,是这样吗?
2.如果java线程已经从内核线程拿到数据,但是还没执⾏完业务流程,现在对应的内核线程明显已经⽆事可⼲了,也会⼀直存在吗?存在的话,略显浪费呀。
请⽼师解答⼀下!
2019-06-29 09:59
作者回复
1、内核线程⼀直存在的,是通过调度器进⾏调度给⽤户线程使⽤的。

2、是的,如果⼤量⽤户线程创建,⻓时间不释放,会受限内核线程资源⽽影响系统性能。
2019-06-30 11:13