当前位置:   article > 正文

十五、线程池

十五、线程池

(一)线程池的执行流程

ThreadPoolExecutor中提供了两种执行任务的方法:

  1. void execute(Runnable command)
  2. Future submit(Runnable task)

实际上submit中最终还是调用的execute()方法,只不过会返回一个Future对象,用来获取任务执行结果:

  1. public Future<?> submit(Runnable task) {
  2. if (task == null) throw new NullPointerException();
  3. RunnableFuture<Void> ftask = newTaskFor(task, null);
  4. execute(ftask);
  5. return ftask;
  6. }

执行流程:

注意:提交一个Runnable时,不管当前线程池中的线程是否空闲,只要数量小于核心线程数就会创建新线程。

注意:ThreadPoolExecutor相当于是非公平的,比如队列满了之后提交的Runnable可能会比正在排队的Runnable先执行。


(二)如何创建线程池

2.1 创建线程池

  1. /*
  2. * corePoolSize:核心线程数
  3. * maximumPoolSize:最大线程数
  4. * keepAliveTime:等待阻塞队列中的超时时间,过期后会尝试回收线程
  5. * unit:keepAliveTime单位
  6. * workQueue:任务等待队列
  7. */
  8. ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(2, 2, 60, TimeUnit.SECONDS, new LinkedBlockingQueue<>());

2.2 核心线程数、最大线程数如何设置

我们都知道,线程池中有两个非常重要的参数:

  1. corePoolSize:核心线程数,表示线程池中的常驻线程的个数
  2. maximumPoolSize:最大线程数,表示线程池中能开辟的最大线程个数

那这两个参数该如何设置呢?

我们对线程池负责执行的任务分为三种情况:

  1. CPU密集型任务,比如找出1-1000000中的素数
  2. IO密集型任务,比如文件IO、网络IO
  3. 混合型任务
2.2.1 CPU密集型

CPU密集型任务的特点时,线程在执行任务时会一直利用CPU,所以对于这种情况,就尽可能避免发生线程上下文切换。

比如,现在我的电脑只有一个CPU,如果有两个线程在同时执行找素数的任务,那么这个CPU就需要额外的进行线程上下文切换,从而达到线程并行的效果,此时执行这两个任务的总时间为:任务执行时间*2+线程上下文切换的时间

而如果只有一个线程,这个线程来执行两个任务,那么时间为:任务执行时间*2

所以对于CPU密集型任务,线程数最好就等于CPU核心数,可以通过以下API拿到你电脑的核心数:

Runtime.getRuntime().availableProcessors()

只不过,为了应对线程执行过程发生缺页中断或其他异常导致线程阻塞的请求,我们可以额外在多设置一个线程,这样当某个线程暂时不需要CPU时,可以有替补线程来继续利用CPU。所以,对于CPU密集型任务,我们可以设置线程数为:CPU核心数+1

2.2.2 IO密集型

我们在来看IO型任务,线程在执行IO型任务时,可能大部分时间都阻塞在IO上,假如现在有10个CPU,如果我们只设置了10个线程来执行IO型任务,那么很有可能这10个线程都阻塞在了IO上,这样这10个CPU就都没活干了,所以,对于IO型任务,我们通常会设置线程数为:2*CPU核心数

不过,就算是设置为了2*CPU核心数,也不一定是最佳的,比如,有10个CPU,线程数为20,那么也有可能这20个线程同时阻塞在了IO上,所以可以再增加线程,从而去压榨CPU的利用率。

通常,如果IO型任务执行的时间越长,那么同时阻塞在IO上的线程就可能越多,我们就可以设置更多的线程,但是,线程肯定不是越多越好,我们可以通过以下这个公式来进行计算:

线程数 = CPU核心数 *( 1 + 线程等待时间 / 线程运行总时间 )

  • 线程等待时间:指的就是线程没有使用CPU的时间,比如阻塞在了IO
  • 线程运行总时间:指的是线程执行完某个任务的总时间

我们可以利用jvisualvm抽样来估计这两个时间:

图中表示,在刚刚这次抽样过程中,run()总共的执行时间为538948ms,利用了CPU的时间为86873ms,所以没有利用CPU的时间为538948ms-86873ms。

所以我们可以计算出:

线程等待时间 = 538948ms-86873ms

线程运行总时间 = 538948ms

所以:线程数 = 8 *( 1 + (538948ms-86873ms) / 538948ms )= 14.xxx

所以根据公式算出来的线程为14、15个线程左右。

按上述公式,如果我们执行的任务IO密集型任务,那么:线程等待时间 = 线程运行总时间,所以:

线程数 = CPU核心数 *( 1 + 线程等待时间 / 线程运行总时间 )

= CPU核心数 *( 1 + 1 )

= CPU核心数 * 2

以上只是理论,实际工作中情况会更复杂,比如一个应用中,可能有多个线程池,除开线程池中的线程可能还有很多其他线程,或者除开这个应用还是一些其他应用也在运行,所以实际工作中如果要确定线程数,最好是压测。

比如我写了一个:

  1. @RestController
  2. public class ZhouyuController {
  3. @GetMapping("/test")
  4. public String test() throws InterruptedException {
  5. Thread.sleep(1000);
  6. return "zhouyu";
  7. }
  8. }

这个接口会执行1s,我现在利用apipost来压:

这是在Tomcat默认最大200个线程的请求下的压测结果。

当我们把线程数调整为500:

server.tomcat.threads.max=500

性能就降低了。

总结,我们再工作中,对于:

  1. CPU密集型任务:CPU核心数+1,这样既能充分利用CPU,也不至于有太多的上下文切换成本
  2. IO型任务:建议压测,或者先用公式计算出一个理论值(理论值通常都比较小)
  3. 对于核心业务(访问频率高),可以把核心线程数设置为我们压测出来的结果,最大线程数可以等于核心线程数,或者大一点点,比如我们压测时可能会发现500个线程最佳,但是600个线程时也还行,此时600就可以为最大线程数
  4. 对于非核心业务(访问频率不高),核心线程数可以比较小,避免操作系统去维护不必要的线程,最大线程数可以设置为我们计算或压测出来的结果。

(三)线程发生异常,会被移除线程池吗

答案是会的,那有没有可能核心线程数在执行任务时都出错了,导致所有核心线程都被移出了线程池?

在源码中,当执行任务时出现异常时,最终会执行processWorkerExit(),执行完这个方法后,当前线程也就自然消亡了,但是!processWorkerExit()方法中会额外再新增一个线程,这样就能维持住固定的核心线程数。


(四)线程池中的线程是如何关闭的

线程池中的线程可以通过如下大方式进行关闭:

  • 线程run方法正常执行完毕。
  • 线程抛出异常
  • 手动关闭线程池中的
  1. threadPoolExecutor.shutdown();
  2. threadPoolExecutor.shutdownNow();


(五)线程池中的状态

  • RUNNING:接收新任务并且处理队列中的任务
  • SHUTDOWN:不会接收新任务并且会处理队列中的任务
  • STOP:不会接收新任务并且不会处理队列中的任务,并且会中断在处理的任务(注意:一个任务能不能被中断得看任务本身)
  • TIDYING:所有任务都终止了,线程池中也没有线程了,这样线程池的状态就会转为TIDYING,一旦达到此状态,就会调用线程池的terminated()
  • TERMINATED:terminated()执行完之后就会转变为TERMINATED

这五种状态并不能任意转换,只会有以下几种转换情况:

  1. RUNNING -> SHUTDOWN:手动调用shutdown()触发,或者线程池对象GC时会调用finalize()从而调用shutdown()
  2. (RUNNING or SHUTDOWN) -> STOP:调用shutdownNow()触发,如果先调shutdown()紧着调shutdownNow(),就会发生SHUTDOWN -> STOP
  3. SHUTDOWN -> TIDYING:队列为空并且线程池中没有线程时自动转换
  4. STOP -> TIDYING:线程池中没有线程时自动转换(队列中可能还有任务)
  5. TIDYING -> TERMINATED:terminated()执行完后就会自动转换

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/不正经/article/detail/156087
推荐阅读
相关标签
  

闽ICP备14008679号