并发与多线程【五】——线程池

引言

线程池通过复用线程,避免线程频繁地创建和销毁。Java 的 Executor 工具类中提供了 5 种类型的线程池创建方法,下面看下它们的特点和适用场景。

线程池创建方法及使用场景

如下图所示:

 

1、固定大小线程池

特点是线程数固定,使用无界队列,适用于任务数量不均匀的场景、对内存压力不敏感,但系统负载比较敏感的场景。

2、Cached 线程池

特点是不限制线程数,适用于要求低延迟的短期任务场景。

3、单线程线程池

就是一个线程的固定线程池,适用于需要异步执行但需要保证任务顺序的场景。

4、Scheduled 线程池

适用于定期执行任务场景,支持固定频率定期执行和固定延时定期执行两种方式。

5、工作窃取线程池

使用的是 ForkJoinPool,是固定并行度的多任务队列,适合任务执行时长不均匀的场景。

 

线程池参数解析

线程池除了工作窃取线程池外,都是通过 ThreadPoolExecutor 的不同参数初始化来创建的,创建参数列表如下图所示:

 

第一个参数 corePoolSize:设置核心线程数。默认情况下核心线程会一直存活。

第二个参数 maximumPoolSize:设置最大线程数。决定线程池最多可以创建多少线程。

第三个参数 keepAliveTime 和第四个参数 unit:用来设置线程空闲时间和空闲时间的单位,当线程闲置超过空闲时间就会被销毁。可以通过 allowCoreThreadTimeOut 方法来允许核心线程被回收。

第五个参数 workQueue:设置缓冲队列,设置线程池时常用的三个缓冲队列如上图左下角所示的 ArrayBlockingQueue、LinkedBlockingQueue、SychronousQueue。其中,ArrayBlockingQueue 是一个有界队列,就是指队列有最大容量限制。LinkedBlockingQueue 是一个无界队列,就是队列不限制容量。最后一个是 SynchronousQueue,是一个同步队列,内部没有缓冲区。

第六个参数 threadFactory:设置线程池工厂方法,线程工厂用来创建新线程,可以对线程的一些方法进行定制,例如线程的 group、线程名、优先级等。一般使用默认工厂类即可。

第七个参数 handler:设置线程池满时的拒绝策略。如上图右下角所示有四种拒绝策略,Abort 策略在线程池满后,提交新任务时会报 RejectedExecutionException,这个也是默认的拒绝策略。Discard 策略会在提交任务失败时对任务直接丢弃。CallerRuns 策略会在任务提交失败时,由提交任务的线程直接执行提交的任务。DiscardOldest 策略会丢弃最早提交的任务。

上面说过的线程池都是使用怎样的参数创建的呢?

  • 固定大小线程池创建时,核心和最大线程数都设置成指定的线程数,这样线程池中就只会使用固定大小的线程数。
  • 队列使用无界队列 LinkedBlockingQueue。
  • Single 线程池就是设置线程数为1的固定线程池。
  • Cached 线程池的核心线程数设置为 0,最大线程数是 Integer.MAX_VALUE,主要是通过把缓冲队列设置成 SynchronousQueue,这样只要没有空闲线程就会新建。
  • Scheduled 线程池与前面几种不同的是使用了 DelayedWorkQueue,这是一种按延迟时间获取任务的优先级队列。

线程池执行流程

向线程池提交任务时,可以使用 execute 和 submit,区别就是 submit 可以返回一个 future 对象,通过该对象可以了解任务执行情况,可以取消任务的执行,还可以获取执行结果或执行异常。submit 最终也是通过 execute 执行的。执行流程如下图所示:

 

向线程池提交任务时,会首先判断线程池中的线程数是否大于设置的核心线程数,如果不大于,就创建一个核心线程来执行任务。 

如果大于核心线程数,会判断缓冲队列是否满了,如果没有满,则放入队列,等待线程空闲时,执行任务。

如果队列已经满了,会判断是否达到了线程池设置的最大线程数,如果没有达到,就创建新线程执行任务。

如果达到了最大线程数,则执行指定的拒绝策略。

 

需要注意的是,队列的判断是先于最大线程数判断的,顺序不要搞反。

 

posted @ 2020-02-18 12:12  习惯沉淀  阅读(259)  评论(0编辑  收藏  举报