高并发专题

【高并发】不得不说的线程池与ThreadPoolExecutor

2022-04-21  本文已影响0人  冰河团队

大家好,我是冰河~~

今天,我们一起来简单聊聊线程池中的ThreadPoolExecutor类,好了,不多说了,开始进入今天的正题。

一、抛砖引玉

既然Java中支持以多线程的方式来执行相应的任务,但为什么在JDK1.5中又提供了线程池技术呢?这个问题大家自行脑补,多动脑,肯定没坏处,哈哈哈。。。

说起Java中的线程池技术,在很多框架和异步处理中间件中都有涉及,而且性能经受起了长久的考验。可以这样说,Java的线程池技术是Java最核心的技术之一,在Java的高并发领域中,Java的线程池技术是一个永远绕不开的话题。既然Java的线程池技术这么重要(怎么能说是这么重要呢?那是相当的重要,那家伙老重要了,哈哈哈),那么,本文我们就来简单的说下线程池与ThreadPoolExecutor类。至于线程池中的各个技术细节和ThreadPoolExecutor的底层原理和源码解析,我们会在【高并发专题】专栏中进行深度解析。

引言:本文是高并发中线程池的开篇之作,就暂时先不深入讲解,只是让大家从整体上认识下线程池中最核心的类之一——ThreadPoolExecutor,关于ThreadPoolExecutor的底层原理和源码实现,以及线程池中的其他技术细节的底层原理和源码实现,我们会在【高并发专题】接下来的文章中,进行死磕。

二、Thread直接创建线程的弊端

(1)每次new Thread新建对象,性能差。
(2)线程缺乏统一管理,可能无限制的新建线程,相互竞争,有可能占用过多系统资源导致死机或OOM。
(3)缺少更多的功能,如更多执行、定期执行、线程中断。
(4)其他弊端,大家自行脑补,多动脑,没坏处,哈哈。

三、线程池的好处

(1)重用存在的线程,减少对象创建、消亡的开销,性能佳。
(2)可以有效控制最大并发线程数,提高系统资源利用率,同时可以避免过多资源竞争,避免阻塞。
(3)提供定时执行、定期执行、单线程、并发数控制等功能。
(4)提供支持线程池监控的方法,可对线程池的资源进行实时监控。
(5)其他好处,大家自行脑补,多动脑,没坏处,哈哈。

四、线程池

1.线程池类结构关系

线程池中的一些接口和类的结构关系如下图所示。


后文会死磕这些接口和类的底层原理和源码。

2.创建线程池常用的类——Executors

3.线程池实例的几种状态

注意:不需要对线程池的状态做特殊的处理,线程池的状态是线程池内部根据方法自行定义和处理的。

4.合理配置线程的一些建议

(1)CPU密集型任务,就需要尽量压榨CPU,参考值可以设置为NCPU+1(CPU的数量加1)。
(2)IO密集型任务,参考值可以设置为2*NCPU(CPU数量乘以2)

五、线程池最核心的类之一——ThreadPoolExecutor

1.构造方法

ThreadPoolExecutor参数最多的构造方法如下:

public ThreadPoolExecutor(int corePoolSize,
                              int maximumPoolSize,
                              long keepAliveTime,
                              TimeUnit unit,
                              BlockingQueue<Runnable> workQueue,
                              ThreadFactory threadFactory,
                              RejectedExecutionHandler rejectHandler) 

其他的构造方法都是调用的这个构造方法来实例化对象,可以说,我们直接分析这个方法之后,其他的构造方法我们也明白是怎么回事了!接下来,就对此构造方法进行详细的分析。

注意:为了更加深入的分析ThreadPoolExecutor类的构造方法,会适当调整参数的顺序进行解析,以便于大家更能深入的理解ThreadPoolExecutor构造方法中每个参数的作用。

上述构造方法接收如下参数进行初始化:

(1)corePoolSize:核心线程数量。

(2)maximumPoolSize:最大线程数。

(3)workQueue:阻塞队列,存储等待执行的任务,很重要,会对线程池运行过程产生重大影响。

其中,上述三个参数的关系如下所示:

根据上述三个参数的配置,线程池会对任务进行如下处理方式:

当提交一个新的任务到线程池时,线程池会根据当前线程池中正在运行的线程数量来决定该任务的处理方式。处理方式总共有三种:直接切换、使用无限队列、使用有界队列。

根据上面三个参数,我们可以简单得出如何降低系统资源消耗的一些措施:

接下来,我们继续看ThreadPoolExecutor的构造方法的参数。

(4)keepAliveTime:线程没有任务执行时最多保持多久时间终止
当线程池中的线程数量大于corePoolSize时,如果此时没有新的任务提交,核心线程外的线程不会立即销毁,需要等待,直到等待的时间超过了keepAliveTime就会终止。

(5)unit:keepAliveTime的时间单位

(6)threadFactory:线程工厂,用来创建线程
默认会提供一个默认的工厂来创建线程,当使用默认的工厂来创建线程时,会使新创建的线程具有相同的优先级,并且是非守护的线程,同时也设置了线程的名称

(7)rejectHandler:拒绝处理任务时的策略

如果workQueue阻塞队列满了,并且没有空闲的线程池,此时,继续提交任务,需要采取一种策略来处理这个任务。

线程池总共提供了四种策略:

2.ThreadPoolExecutor提供的启动和停止任务的方法

(1)execute():提交任务,交给线程池执行
(2)submit():提交任务,能够返回执行结果 execute+Future
(3)shutdown():关闭线程池,等待任务都执行完
(4)shutdownNow():立即关闭线程池,不等待任务执行完

3.ThreadPoolExecutor提供的适用于监控的方法

(1)getTaskCount():线程池已执行和未执行的任务总数
(2)getCompletedTaskCount():已完成的任务数量
(3)getPoolSize():线程池当前的线程数量
(4)getCorePoolSize():线程池核心线程数
(5)getActiveCount():当前线程池中正在执行任务的线程数量

好了,今天就到这儿吧,我是冰河,我们下期见~~

上一篇下一篇

猜你喜欢

热点阅读