线程池-ThreadPoolExecutor解析

一、线程池类:java.util.concurrent.ThreadPoolExecutor,调用构造方法

线程池-ThreadPoolExecutor解析

下面解释下一下构造器中各个参数的含义:

corePoolSize:核心池的大小,在创建了线程池后,默认情况下,线程池中并没有任何线程,而是等待有任务到来才创建线程去执行任务,除非调用了prestartAllCoreThreads()或者prestartCoreThread()方法,从这2个方法的名字就可以看出,是预创建线程的意思,即在没有任务到来之前就创建corePoolSize个线程或者一个线程。默认情况下,在创建了线程池后,线程池中的线程数为0,当有任务来之后,就会创建一个线程去执行任务,当线程池中的线程数目达到corePoolSize后,就会把到达的任务放到缓存队列当中。

maximumPoolSize:线程池最大线程数,它表示在线程池中最多能创建多少个线程。传参时该值一定要不小于corePoolSize,否则线程池创建不了,报非法参数异常。

keepAliveTime:表示线程没有任务执行时最多保持多久时间会终止。默认情况下,当线程池中的线程数大于corePoolSize时,如果一个线程空闲的时间达到keepAliveTime,则会终止,直到线程池中的线程数不超过corePoolSize。但是如果调用了allowCoreThreadTimeOut(boolean)方法,在线程池中的线程数不大于corePoolSize时,keepAliveTime参数也会起作用,直到线程池中的线程数为0。

unit:参数keepAliveTime的时间单位,有7种取值,在TimeUnit类中有7种静态属性:DAY、HOURS、MINUTES等。

workQueue:一个阻塞队列,用来存储等待执行的任务。一般使用LinkedBlockingQueue和SynchronousQueue。常用队列如下:

ArrayBlockingQueue(int i):规定大小的BlockingQueue,其构造必须指定大小。其所含的对象是FIFO顺序排序的。

LinkedBlockingQueue()或者(int i):大小不固定的BlockingQueue,若其构造时指定大小,生成的BlockingQueue有大小限制,不指定大小,其大小由Integer.MAX_VALUE来决定。其所含的对象是FIFO顺序排序的。

PriorityBlockingQueue()或者(int i):类似于LinkedBlockingQueue,若不指定大小则无边界,但是其所含对象的排序不是FIFO,而是依据对象的自然顺序或者构造函数的Comparator决定。

SynchronizedQueue():特殊的BlockingQueue,内部容量为0,每次删除都要等待插入操作,每次插入都要等待删除操作,即放和取交替完成。

threadFactory:线程工厂,主要用来创建线程。一般用Executors.defaultThreadFactory()。

handler:表示当拒绝处理任务时的策略,有如下四种取值:

ThreadPoolExecutor.AbortPolicy:丢弃任务并抛出RejectedExecutionException异常。

ThreadPoolExecutor.DiscardPolicy:也是丢弃任务,但是不抛出异常。

ThreadPoolExecutor.DiscardOldestPolicy:丢弃队列最前面的任务,然后重新尝试执行任务(重复此过程)

ThreadPoolExecutor.CallerRunsPolicy:由调用线程处理该任务

 

二、ThreadPoolExecutor常用API

excute:它是Executor中声明的方法,在ThreadPoolExecutor进行了具体的实现,这个方法是ThreadPoolExecutor的核心方法,通过这个方法可以向线程池提交一个任务,交由线程池去执行。

submit:是在ExecutorService中声明的方法,在AbstractExecutorService就已经有了具体的实现,在ThreadPoolExecutor中并没有对其进行重写,这个方法也是用来向线程池提交任务的,但是它和execute()方法不同,它能够返回任务执行的结果,去看submit()方法的实现,会发现它实际上还是调用的execute()方法,只不过它利用了Future来获取任务执行结果,注意,这里只能读到线程池安排线程处理任务或取消任务的结果,对于线程池拒收的没有线程处理的任务,future是读取不到结果的。

shutdown:关闭线程,当线程池调用该方法时,线程池的状态则立刻变成SHUTDOWN状态。此时,则不能再往线程池中添加任何任务,否则将会抛出RejectedExecutionException异常。但是,此时线程池不会立刻退出,直到添加到线程池中的任务都已经处理完成,才会退出。

shutdownNow:是用来关闭线程池的。执行该方法,线程池的状态立刻变成STOP状态,并试图停止所有正在执行的线程,不再处理还在池队列中等待的任务,当然,它会返回那些未执行的任务。 它试图终止线程的方法是通过调用Thread.interrupt()方法来实现的。

 

三、线程池状态

线程池-ThreadPoolExecutor解析

RUNNING:线程池处在RUNNING状态时,能够接收新任务,以及对已添加的任务进行处理。线程池的初始化状态是RUNNING。换句话说,线程池被一旦被创建,就处于RUNNING状态,并且线程池中的任务数为0!

SHUTDOWN:线程池处在SHUTDOWN状态时,不接收新任务,但能处理已添加的任务。调用线程池的shutdown()接口时,线程池由RUNNING -> SHUTDOWN。

STOP:线程池处在STOP状态时,不接收新任务,不处理已添加的任务,并且会中断正在处理的任务。调用线程池的shutdownNow()接口时,线程池由(RUNNING or SHUTDOWN ) -> STOP。

TIDYING:当所有的任务已终止,ctl记录的”任务数量”为0,线程池会变为TIDYING状态。当线程池变为TIDYING状态时,会执行钩子函数terminated()。terminated()在ThreadPoolExecutor类中是空的,若用户想在线程池变为TIDYING时,进行相应的处理;可以通过重载terminated()函数来实现。 当线程池在SHUTDOWN状态下,阻塞队列为空并且线程池中执行的任务也为空时,就会由 SHUTDOWN -> TIDYING。 另外当线程池在STOP状态下,线程池中执行的任务为空时,就会由STOP -> TIDYING。

TERMINATED:线程池彻底终止,就变成TERMINATED状态。线程池处在TIDYING状态时,执行完terminated()之后,就会由 TIDYING -> TERMINATED。

 

四、任务的执行

这里主要看 new ThreadPoolExecutor与execute方法以及submit方法。

线程池-ThreadPoolExecutor解析

线程池构造方法没什么,就是加了下size的大小验证然后给该池的一些属性赋值。

线程池-ThreadPoolExecutor解析

execute方法具体步骤如英文注释所示:即当前运行线程数量少于corePoolSize则新建线程运行任务,若corePoolSize线程数已使用则任务要排队,若任务进入队列成功,则会去检查线程情况看能否进入到线程中或新开一个线程执行。若队列已满,则线程池会尝试新开线程运行这个任务,若创建线程失败即线程池的线程数量饱和或线程池被shutdown,则会线程池会拒绝处理这个任务。

 

submit方法基本是用RunnableFuture封装了下task然后调用execute方法。

线程池-ThreadPoolExecutor解析

具体执行代码以及流程不分析了,有人分析过了,这里只总结:

1、提交任务(submit)。

2、判断任务是否空,若为空则抛异常结束,若不是则封装Task调用execute方法。

3、判断核心线程数是否饱和,若不饱和则直接新建线程运行任务(execute方法开始)。

4、若核心线程数已饱和,则任务入队列,若入队列成功,则排队等待核心线程处理,任务具体执行顺序看使用的队列是怎样的。

5、若入队列失败(比如线程池被shutdown了或队列已满,这里讨论队列已满导致入队失败的情况)。则看线程数有没有超过maximumPoolSize,若没有则新建线程运行任务。

6、当运行线程数为maximumPoolSize且队伍已满时,任务被拒收即该线程池不处理这些任务,拒收策略如上线程池构造函数handler参数所示。

7、线程运行完任务后,线程就会按策略看是否保留。保留策略看上面的构造函数的keepAliveTime参数。

8、线程池不会主动关闭,即使主线程关闭了,线程池也不会关闭,除非手动对线程池做shutdown处理。

 

 

另外附上演示线程池处理策略例子:

线程池-ThreadPoolExecutor解析

运行结果如下图所示:

线程池-ThreadPoolExecutor解析

线程池-ThreadPoolExecutor解析

 

可以看到,当线程池满负荷运行maximumPoolSize的线程数量且队列塞满了任务的话(采用ThreadPoolExecutor.AbortPolicy(也是线程池默认的handler)策略),后面的要加的4个任务都未运行,主要是加第17个任务时,抛RejectedExecutionException终止了循环直接进入catch代码里然后跳出catch往下执行。注意,这里不catch这个异常的话,主线程会因抛异常而运行不下去,也就不会打印Future。且这里还能看到,这里线程是先运行核心线程以及非核心线程的10个任务再运行队列里的6个任务,并不是先加的任务就先运行,后面非核心线程的任务也能比队列中先加的任务先运行。

 

 

 

参考资料:

https://www.cnblogs.com/dolphin0520/p/3932921.html

https://blog.****.net/shahuhubao/article/details/80311992

https://blog.****.net/fenglllle/article/details/82790242

https://blog.****.net/hgd613/article/details/87903471