线程池工作原理和参数调优

一、Java线程的启动和停止

 1、线程的启动:

        启动Java新线程的唯一方式是执行线程Thread对象的start方法,start方法内部会调用run方法,run方法内的代码也就是这个线程要执行的逻辑。一个Thread对象只能启动一次,多次执行start方法会直接抛出异常。

2、线程的停止:

        停止线程唯一合理的方式是让run方法执行完成,run方法中的代码执行完成,线程则终止。如果我们希望线程在执行过程中的某个位置终止,则其实是需要自行设计代码逻辑,让代码提前执行完成——不论是通过if判断还是其他任何方式。当然,其实停止线程还有一种方式就是抛异常,如果在代码中有一个异常没有被catch,则它将顺着方法调用栈一路上抛最终被抛出最外层的run方法,进而直接阻断程序执行,线程随之终止。

二、jdk线程池的重要参数

    jdk线程池最基本的实现类是ThreadPoolExecutor,创建一个线程池即new一个ThreadPoolExecutor对象,它的构造方法参数就是线程池最重要的配置项,包括:1、核心线程数。2、最大线程数。3、存活时间。4、时间单位。5、阻塞队列。6、线程工厂。7、拒绝策略。

1、核心线程数:

        所谓线程池就是维护几个一直存活的线程对象,避免每次要用新线程时都要new一个新Thread,核心线程数就是这个线程池维护的一直存活的线程数量。那么如何让一个线程一直存活,并执行一个又一个的任务?后文将会简要解答原理。

2、阻塞队列:

        每次执行线程池的execute方法,就是提交一个新任务给它执行,如果所有的核心线程都有正在执行的任务,那么新提交的任务就需要先放到阻塞队里,等到哪个核心线程的任务执行结束了,就会到阻塞队列里来取新任务执行。

3、最大线程数:

        对于jdk的线程池,这是一个很容易理解错的参数,假如核心线程数为10,队列数为10,最大线程数为20,则当我们向线程池提交21个任务时,会先安排10个线程来执行前10个任务,第11至20个任务则会被放到阻塞队里暂存,当提交第21个任务时,由于核心数用尽、队列也用尽,此时会产生第11个线程,来执行第21个任务。即当我们不停往线程池提交任务时,会按照核心线程数 -> 阻塞队列 -> 最大线程数依次使用的方式来确定这个任务该执行还是放入队列等待。

4、存活时间和时间单位:

        当线程数超过核心线程数时,超出的线程存活一会儿没有任务的话就会终止。

5、线程工厂:

        指定线程的创建细节,例如可以取个统一的名字前缀,后续查日志的时候方便看。

6、拒绝策略:

        核心线程数->阻塞队列->最大线程数都用尽了,再提交任务,就触发拒绝策略,常见的拒绝策略如抛异常、交给提交任务的线程去执行(任务变成同步了)等。

三、线程池的一些基本原理

1、核心线程怎样做到长期存活:

        线程池中的线程叫Worker,其重写了run方法使用一个while(xxx)循环来执行代码逻辑,这就做到了只要while的参数是true,这个线程就在while循环里不会终止。

2、线程怎样获取要执行的任务:

        我们通过execute方法来向线程池提交任务,execute方法的参数是Runnable类型,如果池中线程数小于配置的核心线程数,则新建线程,并把这个Runnable对象交给新线程的run方法间接调用执行,否则放入阻塞队列,由于线程while循环的存在,空闲的线程会自动getTask获取这个新任务对象然后执行其run方法(run方法当然不是只有线程启动时的start方法可以调,它同时也是一个普通的方法而已!)

3、线程池的任务执行完成了怎么办:

        线程在while(xxx)循环,但是并不是在一直执行代码,而是在getTask方法执行时会阻塞住,否则就太消耗性能了。getTask方法就是从阻塞队列中取Runnable任务,如果没有可执行的任务,这个方法会阻塞,其阻塞的原理根据阻塞队列的具体实现不同而不同,总体上都是类似于wait、notify方法,这里就不深入解释了。

四、线程池参数调优

        知道了线程池的核心数->队列数->最大数,这个基本工作原理,参数调优就比较简单了。

1、核心线程数:

        对于计算密集型程序,核心线程数无疑和cpu核数相近最佳,8核cpu就设置核心线程数为8。但是现在大多数的java应用都是web应用,而不是纯计算的应用,试想如果我们提交的任务中代码逻辑是读取文件、查询数据库、调用外部接口,则这些逻辑相当于非常耗时,此时应该适当扩大核心线程数,来提高cpu的利用率,如果线程代码大多时间都在等待io操作,cpu完全有能力来调度更多的线程并发执行。以netty为例,为平衡io操作的耗时,其线程池默认为cpu核数*2,实际上我们还可以使用多个线程池来分离计算密集型和io密集型的任务,io密集型的线程池核心数可以根据经验调整为几十个至几百个。

2、队列数:

        阻塞队列的大小根据业务经验来配置,高峰期当核心数用尽时,任务可以暂时存放在队列中,用来缓冲压力。过小则不起到缓冲作用。过大则可能占用资源,导致任务积压并且可能导致一些原本的代码问题长时间不显现,提高问题排查的难度。

3、最大线程数:

        在较多数情况下,由于阻塞队列的存在和jdk线程池的工作机制,最大线程数可能不太容易用到,一种常见的实践是设置最大数=核心数,同时适当调大该值,即认为队列满了的情况是极端异常情况,不必再继续创建线程执行任务,而是直接执行拒绝策略。

        但是注意:有个别框架的线程池是在jdk基础上重写的,如tomcat的接收连接线程池,调整了jdk原本的策略,改为了核心数->最大数->阻塞队列这样的用尽顺序,所以其在进行请求并发控制时需要配置的参数是最大线程数maxThreads。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

坏猫警长

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值