Java线程池

273 阅读17分钟

概述

引入线程池的目的

相比于单独创建线程执行任务,引入线程池有如下好处:

  1. 降低系统资源消耗,通过重用已存在的线程,降低线程创建和销毁造成的消耗;
  2. 提高系统响应速度,当有任务到达时,无需等待新线程的创建便能立即执行;
  3. 方便线程并发数的管控,线程若是无限制的创建,不仅会额外消耗大量系统资源,更是占用过多资源而阻塞系统或oom等状况,从而降低系统的稳定性。线程池能有效管控线程,统一分配、调优,提供资源使用率;
  4. 更强大的功能,线程池提供了定时、定期以及可控线程数等功能的线程池,使用方便简单。

线程池的使用

线程池执行流程

image.png

  • 当线程数小于核心线程数时,会一直创建线程直到线程数等于核心线程数;
  • 当线程数等于核心线程数时,新加入的任务会被放到任务队列等待执行;
  • 当任务队列已满,又有新的任务时,会创建线程直到线程数量等于最大线程数;
  • 当线程数等于最大线程数,且任务队列已满时,新加入任务会被拒绝。

线程池的创建及核心参数

线程池的创建

 ExecutorService pool = new ThreadPoolExecutor(1, 2, 1000, 
                                               TimeUnit.MILLISECONDS,
                                               new SynchronousQueue<Runnable>(),
                                               Executors.defaultThreadFactory(),
                                               new ThreadPoolExecutor.AbortPolicy());

根据ThreadPoolExecutor类的构造方法,学习其线程池的核心参数

 public ThreadPoolExecutor(int corePoolSize,
                           int maximumPoolSize,
                           long keepAliveTime,
                           TimeUnit unit,
                           BlockingQueue<Runnable> workQueue,
                           ThreadFactory threadFactory,
                           RejectedExecutionHandler handler) {
     if (corePoolSize < 0 ||
         maximumPoolSize <= 0 ||
         maximumPoolSize < corePoolSize ||
         keepAliveTime < 0)
         throw new IllegalArgumentException();
     if (workQueue == null || threadFactory == null || handler == null)
         throw new NullPointerException();
     this.corePoolSize = corePoolSize;
     this.maximumPoolSize = maximumPoolSize;
     this.workQueue = workQueue;
     this.keepAliveTime = unit.toNanos(keepAliveTime);
     this.threadFactory = threadFactory;
     this.handler = handler;
 }
  • corePoolSize(必需):核心线程数

    • 核心线程会一直存在,即使没有任务执行;
    • 当线程数小于核心线程数的时候,即使有空闲线程,也会一直创建线程直到达到核心线程数;
    • 设置allowCoreThreadTimeout=true(默认false)时,核心线程会超时关闭
  • maximumPoolSize(必需):线程池所能容纳的最大线程数 = 核心线程数 + 临时线程数

    • 线程池里允许存在的最大线程数量;
    • 当任务队列已满,且线程数量大于等于核心线程数时,会创建新的线程执行任务;
    • 线程池里允许存在的最大线程数量。当任务队列已满,且线程数量大于等于核心线程数时,会创建新的线程执行任务。
  • keepAliveTime(必需):线程存活保持时间

    • 当线程空闲时间超过keepAliveTime时,非核心线程就会被回收;
    • 如果设置了allowCoreThreadTimeout=true,核心线程也会退出直到线程数等于零。。
  • unit(必需):指定 keepAliveTime 参数的时间单位,时间单位,分为7类,从细到粗顺序

    • NANOSECONDS(纳秒)
    • MICROSECONDS(微妙)
    • MILLISECONDS(毫秒)
    • SECONDS(秒)
    • MINUTES(分)
    • HOURS(小时)
    • DAYS(天);
  • workQueue(必需):任务队列

    • 用于传输和保存等待执行任务的阻塞队列。可以使用此队列与线程池进行交互:
    • 如果运行的线程数少于 corePoolSize,则 Executor 始终首选添加新的线程,而不进行排队。
    • 如果运行的线程数等于或多于 corePoolSize,则 Executor 始终首选将请求加入队列,而不添加新的线程。
    • 如果无法将请求加入队列,则创建新的线程,除非创建此线程超出 maximumPoolSize,在这种情况下,任务将被拒绝。
  • threadFactory(可选):线程工厂

    • 用于指定为线程池创建新线程的方式
  • handler(可选):拒绝策略

    • 当线程池和队列都满了,则表明该线程池已达饱和状态。

    • ThreadPoolExecutor.AbortPolicy:处理程序遭到拒绝,则直接抛出运行时异常 RejectedExecutionException。(默认策略)

    • ThreadPoolExecutor.CallerRunsPolicy:调用者所在线程来运行该任务,此策略提供简单的反馈控制机制,能够减缓新任务的提交速度。

    • ThreadPoolExecutor.DiscardPolicy:无法执行的任务将被删除。

    • ThreadPoolExecutor.DiscardOldestPolicy:如果执行程序尚未关闭,则位于工作队列头部的任务将被删除,然后重新尝试执行任务(如果再次失败,则重复此过程)。

向线程池提交任务

可以使用两个方法向线程池提交任务,分别为 execute()和 submit()方法。

execute()方法用于提交不需要返回值的任务,所以无法判断任务是否被线程池执行成功。

submit()方法用于提交需要返回值的任务。线程池会返回一个 future 类型的对象,通过这个 future 对象可以判断任务是否执行成功,并且可以通过 future 的 get()方法来获取返回值,get()方法会阻塞当前线程直到任务完成,而使用 get(long timeout,TimeUnit unit)方法则会阻塞当前线程一段时间后立即返回,这时候有可能任务没有执行完

 // 执行任务
 void execute(Runnable command);
 ​
 ​
 // 提交任务 task,用返回值 Future 获得任务执行结果
 <T> Future<T> submit(Callable<T> task);
 ​
 ​
 // 提交 tasks 中所有任务
 <T> List<Future<T>> invokeAll(Collection<? extends Callable<T>> tasks) throws InterruptedException;
 ​
 ​
 // 提交 tasks 中所有任务,带超时时间
 <T> List<Future<T>> invokeAll(Collection<? extends Callable<T>> tasks,long timeout, TimeUnit unit)throws InterruptedException;
 ​
 ​
 // 提交 tasks 中所有任务,哪个任务先成功执行完毕,返回此任务执行结果,其它任务取消
 <T> T invokeAny(Collection<? extends Callable<T>> tasks)throws InterruptedException, ExecutionException;
 ​
 ​
 // 提交 tasks 中所有任务,哪个任务先成功执行完毕,返回此任务执行结果,其它任务取消,带超时时间
 <T> T invokeAny(Collection<? extends Callable<T>> tasks,long timeout, TimeUnit unit)throws InterruptedException, ExecutionException, TimeoutException;

可以看到execute() 接受一个Runnable类的实例

submit()接受一个Callable类的实例

关闭线程池

可以通过调用线程池的 shutdown 或 shutdownNow 方法来关闭线程池。

原理:是遍历线程池中的工作线程,然后逐个调用线程的 interrupt 方法来中断线程,所以无法响应中断的任务可能永远无法终止

shutdown

线程池状态变为 SHUTDOWN

  1. 不会接收新任务
  2. 但已提交任务会执行完
  3. 此方法不会阻塞调用线程的执行

shutdown源码

 public void shutdown() {
     final ReentrantLock mainLock = this.mainLock;
     mainLock.lock();
     try {
         checkShutdownAccess();
         // 修改线程池状态为SHUTDOWN
         advanceRunState(SHUTDOWN);
         // 仅会打断空闲线程
         interruptIdleWorkers();
         onShutdown(); // 扩展点 ScheduledThreadPoolExecutor
     } finally {
         mainLock.unlock();
     }
     // 尝试终结(没有运行的线程可以立刻终结,如果还有运行的线程也不会等)
     tryTerminate();
 }

shutdownNow

线程池状态变为 STOP

  1. 不会接收新任务
  2. 会将队列中的任务返回
  3. 并用 interrupt 的方式中断正在执行的任务

shutdownNow源码

 public List<Runnable> shutdownNow() {
     List<Runnable> tasks;
     final ReentrantLock mainLock = this.mainLock;
     mainLock.lock();
     try {
         checkShutdownAccess();
         // 修改线程池状态为STOP
         advanceRunState(STOP);
         // 打断所有线程
         interruptWorkers();
         // 获取队列中剩余任务
         tasks = drainQueue();
     } finally {
         mainLock.unlock();
     }
     // 尝试终结
     tryTerminate();
     return tasks; 
 }

shutdown 或 shutdownNow 方法的差别:

  1. shutdownNow 首先将线程池的状态设置成 STOP,然后尝试停止所有的正在执行或暂停任务的线程,并返回等待执行任务的列表,
  2. shutdown 只是将线程池的状态设置成 SHUTDOWN 状态,然后中断所有没有正在执行任务的线程。

只要调用了这两个关闭方法中的任意一个,isShutdown 方法就会返回 true。

当所有的任务都已关闭后,才表示线程池关闭成功,这时调用 isTerminaed 方法会返回 true。

至于应该调用哪一种方法来关闭线程池,应该由提交到线程池的任务特性决定,通常调用shutdown 方法来关闭线程池,如果任务不一定要执行完,则可以调用 shutdownNow 方法

线程池的监控及扩展

如果在系统中大量使用线程池,则有必要对线程池进行监控,方便在出现问题时,可以根据线程池的使用状况快速定位问题。可以通过线程池提供的参数进行监控,在监

控线程池的时候可以使用以下属性。

  • taskCount:线程池需要执行的任务数量。
  • completedTaskCount:线程池在运行过程中已完成的任务数量,小于或等于taskCount。
  • largestPoolSize:线程池里曾经创建过的最大线程数量。通过这个数据可以知道线程池是否曾经满过。如该数值等于线程池的最大大小,则表示线程池曾经满过。
  • getPoolSize:线程池的线程数量。如果线程池不销毁的话,线程池里的线程不会自动销毁,所以这个大小只增不减。
  • getActiveCount:获取活动的线程数
 /**
  * 线程池任务执行类
  */
 public class ThreadPoolTask implements Runnable, Serializable {
     private Integer threadPoolTaskData;
     private ThreadPoolExecutor threadPool;
  
     ThreadPoolTask(Integer tasks, ThreadPoolExecutor threadPool) {
         this.threadPoolTaskData = tasks;
         this.threadPool = threadPool;
     }
  
     public void run() {
         System.out.println("------------------------------");
         System.out.println("计划需要执行的近似任务数:" + threadPool.getTaskCount());
         System.out.println("已完成执行的近似任务总数:" + threadPool.getCompletedTaskCount());
         System.out.println("池中曾出现过的最大线程数:" + threadPool.getLargestPoolSize());
         System.out.println("返回线程池中的当前线程数:" + threadPool.getPoolSize());
         System.out.println("线程池中的当前活动线程数:" + threadPool.getActiveCount());
         System.out.println("线程池中约定的核心线程数:" + threadPool.getCorePoolSize());
         System.out.println("线程池中约定的最大线程数:" + threadPool.getMaximumPoolSize());
         for (int i = 0; i < 2; i++) {
             System.out.println("--------线程名:" + Thread.currentThread().getName() + ":" + (threadPoolTaskData++));
             try {
                 /**用延时来模拟线程在操作*/
                 Thread.sleep(2000);
             } catch (Exception e) {
                 System.out.println("支线程异常:"+e.getMessage());
                 e.printStackTrace();
             }
         }
     }
 }

通过扩展线程池进行监控。可以通过继承线程池来自定义线程池,重写线程池的beforeExecuteafterExecuteterminated 方法,也可以在任务执行前、执行后和线程池关闭前执行一些代码来进行监控。例如,监控任务的平均执行时间、最大执行时间和最小执行时间等。

 // 继承ThreadPoolExecutor重写 beforeExecute afterExecute terminated方法
 public class MyThreadPoolExecutor extends ThreadPoolExecutor {
  
      public MyThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit,
              BlockingQueue<Runnable> workQueue) {
          super (corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue);
      }
      
     // 在任务执行前进行扩展
      @Override
      protected void beforeExecute(Thread t, Runnable r) {
          System.out.println( "beforeExecute MyThread Name:"  + t.getName() + ",TID:"  + t.getId());
      }
      
     // 在任务执行后进行扩展
      @Override
      protected void afterExecute(Runnable r, Throwable t) {
          System.out.println( "afterExecute TID:"  + Thread.currentThread().getId());
          System.out.println( "afterExecute PoolSize:"  +  this .getPoolSize());
      }
      
     // 在线程池关闭前进行扩展
      @Override
      protected void terminated() {
          System.out.println( "terminated" );
      }
      
      
      public static void main(String[] args) {
          MyThreadPoolExecutor myThreadPoolExecutor =  new  MyThreadPoolExecutor(2, 2, 0, TimeUnit.SECONDS, new  LinkedBlockingQueue<Runnable>());
          for  (int i = 0; i < 10; i++) {
              myThreadPoolExecutor.submit( new  Runnable() {
                  public void run() {
                      try  {
                          TimeUnit.SECONDS.sleep(1);
                          System.out.println( "执行完" );
                      }  catch  (InterruptedException e) {
                          e.printStackTrace();
                      }
                  }
              });
          }
          // 关闭线程池
          myThreadPoolExecutor.shutdownNow();
      }
  
 }

Executor框架

Executor的类图

image.png

Executor 框架主要由 3 大部分组成如下。

  1. 任务。包括被执行任务需要实现的接口:Runnable 接口或 Callable 接口。
  2. 任务的执行。包括任务执行机制的核心接口 Executor,以及继承自 Executor 的ExecutorService 接口。Executor 框架有两个关键类实现了 ExecutorService 接口 (ThreadPoolExecutor 和 ScheduledThreadPoolExecutor)。
  3. 异步计算的结果。包括接口 Future 和实现 Future 接口的 FutureTask 类。

ThreadPoolExecutor 通常使用工厂类 Executors 来创建。Executors 可以创建 3 种类型的 ThreadPoolExecutor:SingleThreadExecutor、FixedThreadPool 和 CachedThreadPool。

newCachedThreadPool

 public static ExecutorService newCachedThreadPool() {
         return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
                                       60L, TimeUnit.SECONDS,
                                       new SynchronousQueue<Runnable>());
     }

线程池参数

  • corePoolSize = 0;
  • maximumPoolSize;
  • keepAliveTime = 60L;
  • unit = TimeUnit.SECONDS;
  • workQueue = new SynchronousQueue()

参数说明

  • CachedThreadPool 的corePoolSize 被设置为0,即corePool 为空;maximumPoolSize 被设置为 Integer.MAX_VALUE,即2147483647,为无界的。把keepAliveTime 设置为60L, 意味着CachedThreadPool 中的空闲线程等待新任务的最长时间为60秒,空闲线程超过60秒后将会被终止。
  • CachedThreadPool 使用没有容量的 SynchronousQueue 作为阻塞队列;意味着,如果主线程提交任务的速度高于 maximumPool 中线程处理任务的速度时,CachedThreadPool 会不断创建新线程。极端情况下会创建过多的线程,耗尽 CPU 和内存资源。
  • newCachedThreadPool在没有任务执行时,当线程的空闲时间超过keepAliveTime,会自动释放线程资源,当提交新任务时,如果没有空闲线程,则创建新线程执行任务,会导致一定的系统开销。
  • 使用该线程池时,一定要注意控制并发的任务数,否则创建大量的线程可能导致严重的性能问题。

执行流程

  • 没有核心线程,直接向 SynchronousQueue 中提交任务
  • 如果有空闲线程,就去取出任务执行;如果没有空闲线程,就新建一个
  • 执行完任务的线程有 60 秒生存时间,如果在这个时间内可以接到新任务,就可以继续活下去,否则就拜拜,由于空闲 60 秒的线程会被终止,长时间保持空闲的 CachedThreadPool 不会占用任何资源。CachedThreadPool 用于并发执行大量短期的小任务,或者是负载较轻的服务器。

使用

 // 1. 创建可缓存线程池对象
 ExecutorService cachedThreadPool = Executors.newCachedThreadPool();
 // 2. 创建好Runnable类线程对象 & 需执行的任务
 Runnable task =new Runnable(){
   public void run() {
      System.out.println("执行任务啦");
   }
 };
 // 3. 向线程池提交任务
 cachedThreadPool.execute(task);

newFixedThreadPool

创建一个固定大小的线程池,可控制线程的最大并发数,超出的线程会在LinkedBlockingQueue阻塞队列中等待

可以通过调用Executors类的static newFixedThreadPool()方法获得一个固定线程池。

语法

使用方法 ExecutorService fixedThreadPool = Executors.newFixedThreadPool(n);

  1. 最多n个线程将处于活动状态。
  2. 如果提交了两个以上的线程,那么它们将保持在队列中,直到线程可用。
  3. 线程会一直存在,直到池关闭。

静态方法

 public static ExecutorService newFixedThreadPool(int nThreads) {
     return new ThreadPoolExecutor(nThreads, nThreads,
                                   0L, TimeUnit.MILLISECONDS,
                                   new LinkedBlockingQueue<Runnable>());
 }
 ​
 public static ExecutorService newFixedThreadPool(int nThreads, ThreadFactory threadFactory) {
     return new ThreadPoolExecutor(nThreads, nThreads,
                                   0L, TimeUnit.MILLISECONDS,
                                   new LinkedBlockingQueue<Runnable>(),
                                   threadFactory);
 }
 ​
```java

-   corePoolSize = nThreads;
-   maximumPoolSize = nThreads;
-   keepAliveTime = 0L;
-   unit = TimeUnit.MILLISECONDS;
-   workQueue = new LinkedBlockingQueue<Runnable>()

**参数说明**

-   核心线程数 == 最大线程数,也就是说没有临时线程
-   无需设置临时线程的最大空闲时间
-   阻塞队列是无界的,可以存放任意数量的任务
-   适用于任务量已知,且相对耗时的任务(推荐使用)

### newSingleThreadExecutor

SingleThreadExecutor 是使用单个 worker 线程的 Executor。

SingleThreadExecutor 的 corePoolSize 和 maximumPoolSize 被设置为 1。其他参数与FixedThreadPool 相同

```java
 public static ExecutorService newSingleThreadExecutor() {
     return new FinalizableDelegatedExecutorService
                 (new ThreadPoolExecutor(1, 1,0L, TimeUnit.MILLISECONDS,new LinkedBlockingQueue<Runnable>()));
 }

image.png

  1. 如果当前运行的线程数少于 corePoolSize(即线程池中无运行的线程),则创建一个新线程来执行任务。
  2. 在线程池完成预热之后(当前线程池中有一个运行的线程),将任务加入 LinkedBlockingQueue。
  3. 线程执行完 1 中的任务后,会在一个无限循环中反复从 LinkedBlockingQueue 获取任务来执行。

newScheduledThreadPool

ScheduledThreadPoolExecutor 继承自 ThreadPoolExecutor。它主要用来在给定的延迟之后运行任务,或者定期执行任务。该线程池支持定时的以及周期性的任务执行,支持定时及周期性任务执行

静态方法

 public static ScheduledExecutorService newScheduledThreadPool(int corePoolSize) {
     return new ScheduledThreadPoolExecutor(corePoolSize);
 }
 ​
 ​
 // ScheduledThreadPoolExecutor 构造方法
 public ScheduledThreadPoolExecutor(int corePoolSize,
                                    ThreadFactory threadFactory) {
     super(corePoolSize, Integer.MAX_VALUE, 0, NANOSECONDS,new DelayedWorkQueue(), threadFactory);
 }
  • corePoolSize = 0;
  • maximumPoolSize = Integer.MAX_VALUE;
  • keepAliveTime = 0L;
  • unit = TimeUnit.NANOSECONDS;
  • workQueue = new DelayedWorkQueue()

参数说明

  • 具有核心线程数corePoolSize
  • 最大线程数是Integer.MAX_VALUE
  • 空闲线程的存活时间是0,单位是NANOSECONDS
  • 采用了一个DelayedWorkQueue队列,是一个无界队列。

周期性的实现

ScheduledThreadPoolExecutor 会把待调度的任务(ScheduledFutureTask)放到一个 DelayQueue 中

ScheduledFutureTask 主要包含 3 个成员变量,如下。

  1. long 型成员变量 time,表示这个任务将要被执行的具体时间。
  2. long 型成员变量 sequenceNumber,表示这个任务被添加到ScheduledThreadPoolExecutor 中的序号。
  3. long 型成员变量 period,表示任务执行的间隔周期。

DelayQueue 封装了一个 PriorityQueue,这个 PriorityQueue 会对队列中的 ScheduledFutureTask 进行排序。排序时,time 小的排在前面(时间早的任务将被先执行)。如果两个 ScheduledFutureTask 的 time 相同,就比较 sequenceNumber,sequenceNumber 小的排在前面(也就是说,如果两个任务的执行时间相同,那么先提交的任务将被先执行)。

任务执行过程

image.png

  1. 线程 1 从 DelayQueue 中获取已到期的 ScheduledFutureTask(DelayQueue.take())。到期任务是指 ScheduledFutureTask 的 time 大于等于当前时间。
  2. 线程 1 执行这个 ScheduledFutureTask。
  3. 线程 1 修改 ScheduledFutureTask 的 time 变量为下次将要被执行的时间。
  4. 线程 1 把这个修改 time 之后的 ScheduledFutureTask 放回 DelayQueue 中(DelayQueue.add())。

线程池面试题

为什么使用线程池

  1. 降低系统资源消耗,通过重用已存在的线程,降低线程创建和销毁造成的消耗;
  2. 提高系统响应速度,当有任务到达时,无需等待新线程的创建便能立即执行;
  3. 方便线程并发数的管控,线程若是无限制的创建,不仅会额外消耗大量系统资源,更是占用过多资源而阻塞系统或oom等状况,从而降低系统的稳定性。线程池能有效管控线程,统一分配、调优,提供资源使用率;
  4. 更强大的功能,线程池提供了定时、定期以及可控线程数等功能的线程池,使用方便简单。

线程池执行任务的流程?

image.png

  1. 线程池执行execute/submit方法向线程池添加任务,当任务小于核心线程数corePoolSize,线程池中可以创建新的线程。
  2. 当任务大于核心线程数corePoolSize,就向阻塞队列添加任务。
  3. 如果阻塞队列已满,需要通过比较参数maximumPoolSize,在线程池创建新的线程,当线程数量大于maximumPoolSize,说明当前设置线程池中线程已经处理不了了,就会执行饱和策略。

执行execute()方法和submit()方法的区别是什么呢?

  1. execute() 方法用于提交不需要返回值的任务,所以无法判断任务是否被线程池执行成功与否;
  2. submit()方法用于提交需要返回值的任务。线程池会返回一个future类型的对象,通过这个future对象可以判断任务是否执行成功,并且可以通过future的get()方法来获取返回值,get()方法会阻塞当前线程直到任务完成

线程池的相关参数有哪些

  1. corePoolSize : 核心线程大小。线程池一直运行,核心线程就不会停止。

  2. maximumPoolSize :线程池最大线程数量。非核心线程数量=maximumPoolSize-corePoolSize

  3. keepAliveTime :非核心线程的心跳时间。如果非核心线程在keepAliveTime内没有运行任务,非核心线程会消亡。

  4. TimeUnit(线程活动保持时间的单位):可选的单位有天(DAYS)、小时(HOURS)、分钟(MINUTES)、毫秒(MILLISECONDS)、微秒(MICROSECONDS,千分之一毫秒)和纳秒(NANOSECONDS,千分之一微秒

  5. runnableTaskQueue :任务队列,用于保存等待执行的任务的阻塞队列。可以选择以下几个阻塞队列:

    1. ArrayBlockingQueue:是一个基于数组结构的有界阻塞队列,此队列按 FIFO(先进先出)原则对元素进行排序
    2. LinkedBlockingQueue:一个基于链表结构的阻塞队列,此队列按 FIFO 排序元素,吞吐量通常要高于 ArrayBlockingQueue。静态工厂方法Executors.newFixedThreadPool()使用了这个队列。
    3. SynchronousQueue:一个不存储元素的阻塞队列。每个插入操作必须等到另一个线程调用移除操作,否则插入操作一直处于阻塞状态,吞吐量通常要高于Linked-BlockingQueue,静态工厂方法 Executors.newCachedThreadPool 使用了这个队列。
    4. PriorityBlockingQueue:一个具有优先级的无限阻塞队列。 3)maximumPoolSize(线程池最大数量):线程池允许创建的最大线程数。如果队列满了,并且已创建的线程数小于最大线程数,则线程池会再创建新的线程执行任务。值得注意的是,如果使用了无界的任务队列这个参数就没什么效果。
  6. defaultHandler :拒绝策略。ThreadPoolExecutor类中一共有4种拒绝策略。通过实现RejectedExecutionHandler接口。

    1. AbortPolicy : 线程任务丢弃报错。默认饱和策略。
    2. DiscardPolicy : 线程任务直接丢弃不报错。
    3. DiscardOldestPolicy : 将workQueue队首任务丢弃,将最新线程任务重新加入队列执行。
    4. CallerRunsPolicy :线程池之外的线程直接调用run方法执行。
  7. ThreadFactory :线程工厂。新建线程工厂。

线程池的拒绝策略有哪些?

  • AbortPolicy中止策略: 丢弃任务并抛出RejectedExecutionException异常。 这是线程池默认的拒绝策略,在任务不能再提交的时候,抛出异常,打断当前执行流程,及时反馈程序运行状态。
  • DiscardPolicy丢弃策略: ThreadPoolExecutor.DiscardPolicy:丢弃任务,但是不抛出异常。如果线程队列已满,则后续提交的任务都会被丢弃,且是静默丢弃。使用此策略,可能会使我们无法发现系统的异常状态。建议是一些无关紧要的业务采用此策略。
  • DiscardOldestPolicy弃老策略: 丢弃队列最前面的任务,然后重新提交被拒绝的任务。 此拒绝策略,是一种喜新厌旧的拒绝策略。是否要采用此种拒绝策略,还得根据实际业务是否允许丢弃老任务来认真衡量。
  • CallerRunsPolicy调用者运行策略: 由调用线程处理该任务。 功能:当触发拒绝策略时,只要线程池没有关闭,就由提交任务的当前线程处理。

常用的JAVA线程池有哪几种类型?

  • newCachedThreadPool 创建一个可缓存线程池,如果线程池长度超过处理需要,可灵活回收空闲线程,若无可回收,则新建线程。 工作线程的创建数量几乎没有限制(其实也有限制的,数目为Interger. MAX_VALUE), 这样可灵活的往线程池中添加线程。 如果长时间没有往线程池中提交任务,即如果工作线程空闲了指定的时间(默认为1分钟),则该工作线程将自动终止。终止后,如果你又提交了新的任务,则线程池重新创建一个工作线程。 在使用CachedThreadPool时,一定要注意控制任务的数量,否则,由于大量线程同时运行,很有会造成系统OOM。
  • newFixedThreadPool 创建一个指定工作线程数量的线程池。每当提交一个任务就创建一个工作线程,如果工作线程数量达到线程池初始的最大数,则将提交的任务存入到池队列中。 FixedThreadPool是一个典型且优秀的线程池,它具有线程池提高程序效率和节省创建线程时所耗的开销的优点。但是,在线程池空闲时,即线程池中没有可运行任务时,它不会释放工作线程,还会占用一定的系统资源。
  • newSingleThreadExecutor 创建一个单线程化的Executor,即只创建唯一的工作者线程来执行任务,它只会用唯一的工作线程来执行任务,保证所有任务按照指定顺序(FIFO, LIFO, 优先级)执行。如果这个线程异常结束,会有另一个取代它,保证顺序执行。单工作线程最大的特点是可保证顺序地执行各个任务,并且在任意给定的时间不会有多个线程是活动的。
  • newScheduleThreadPool 创建一个定长的线程池,而且支持定时的以及周期性的任务执行,支持定时及周期性任务执行。

Executor和Executors的区别?

  • Executors 工具类的不同方法按照我们的需求创建了不同的线程池,来满足业务的需求。
  • Executor 接口对象能执行我们的线程任务。ExecutorService接口继承了Executor接口并进行了扩展,提供了更多的方法我们能获得任务执行的状态并且可以获取任务的返回值。
  • 使用ThreadPoolExecutor 可以创建自定义线程池。Future 表示异步计算的结果,他提供了检查计算是否完成的方法,以等待计算的完成,并可以使用get()方法获取计算的结果。

线程池是如何关闭的?

  • 我们可以通过调用线程池的 shutdown 或 shutdownNow 方法来关闭线程池
  • 两者的核心原理都是遍历线程池中的工作线程,然后逐个调用线程的 interrupt 方法来中断线程,所以无法响应中断的任务可能永远无法终止。
  • 只要调用了这两个关闭方法中的任意一个,isShutdown 方法就会返回 true。
  • 当所有的任务都已关闭后,才表示线程池关闭成功,这时调用 isTerminaed 方法会返回 true。

比较下shutdownNow 和 shutdown的区别?

  1. shutdownNow 首先将线程池的状态设置成 STOP,然后尝试停止所有的正在执行或暂停任务的线程,并返回等待执行任务的列表,
  2. shutdown 只是将线程池的状态设置成 SHUTDOWN 状态,然后中断所有没有正在执行任务的线程,并且已提交任务会执行完