线程池及ThreadPoolExecutor源码分析

简单介绍

开启掘金成长之旅!这是我参与「掘金日新计划 · 12 月更文挑战」的第5天,点击查看活动详情

线程池的基本认识

什么是线程池?

线程池(Thread Pool)是一种基于「池化思想」管理线程的工具,经常出现在多线程服务器中,如MySQL

提前创建好若干个线程放在一个容器中。如果有任务需要处理,则将任务直接分配给线程池中的线程来执行,任务处理完以后这个线程不会被销毁,而是等待后续分配任务

使用线程池有什么好处?

  • 降低创建线程和销毁线程的性能开销

    通过池化技术复用已创建的线程,降低线程创建和销毁造成的损耗

  • 提高相应速度

    当有新任务需要执行时不需要等待线程创建就可以立马执行

  • 提高线程的可管理性

    线程是稀缺资源,如果无限制创建,不仅会消耗系统资源,还会因为线程的不合理分布导致资源调度失衡,降低系统的稳定性。使用线程池可以进行统一的分配、调优和监控。 合理的的设置线程池大小可以避免因为线程数超过硬件资源瓶颈带来的问题

  • 提供更多更强大的功能

    线程池具备可扩展性,允许开发人员向其中增加更多的功能,比如延时定时线程池ScheduledThreadPoolExecutor,允许任务延时执行或定期执行。

使用线程池为了解决什么问题?

线程池要解决的核心问题就是资源管理问题

在并发环境下,系统不能够确定在任意时刻中,有多少任务需要执行,有多少资源需要投入。这种不确定性将带来以下若干问题:

  • 频繁申请/销毁资源和调度资源,将带来额外的消耗,可能会非常巨大。
  • 对资源无限申请缺少抑制手段,易引发系统资源耗尽的风险。
  • 系统无法合理管理内部的资源分布,会降低系统的稳定性。

也是为了解决上述问题,所以线程池采用了「池化思想」。这个内容将在下面的「线程池原理分析」中解释。

池化思想

什么是池化思想?

池化是将资源统一在一起管理的一种思想,目的是为了最大化收益并最小化风险

在计算机领域中的表现为:统一管理IT资源,包括服务器、存储、和网络资源等等。通过共享资源。使用户在低投入中获益。

除去线程池,还有其他比较典型的几种使用策略包括:

  • 内存池(Memory Pooling):

    预先申请内存,提升申请内存速度,减少内存碎片。

  • 连接池(Connection Pooling):

    预先申请数据库连接,提升申请连接的速度,降低系统的开销。

  • 实例池(Object Pooling):

    循环使用对象,减少资源在初始化和释放时的昂贵损耗。

接着我们将分析Java中的线程池核心实现类--ThreadPoolExecutor

ThreadPoolExecutor 分析

Java如何实现和管理线程池的?

从JDK 5开始,JDK把工作单元与执行机制分离开来,工作单元包括RunnableCallable,而执行机制由Executor框架提供。

Java线程池实现原理其实就是一个线程集合threadSet和一个阻塞队列blockQueue

当用户向线程池提交一个任务(也就是线程)时,线程池会先将任务放入blockQueue中。threadSet中的线程会不断的从blockQueue中获取线程然后执行。当blockQueue中没有任务的时候,thread就会阻塞,直到队列中有任务了就取出来继续执行。

这里我们画图表示一下这个模型:

image.png

当然上图只是一个简略的模型,接着我们会在「ThreadPoolExecutor运行机制」中画出更加详细的模型。

ThreadPoolExecutor 运行机制

ThreadPoolExecutorUML

image.png

  • Executor接口

    ThreadPoolExecutor实现的顶层接口是Executor,顶层接口Executor提供了一种思想:

    将任务提交和任务执行进行解耦。用户无需关注如何创建线程,如何调度线程来执行任务,用户只需提供Runnable对象,将任务的运行逻辑提交到执行器(Executor)中,由Executor框架完成线程的调配和任务的执行部分。

  • ExecutorService接口

    ExecutorService接口增加了一些能力:

    • 扩充执行任务的能力,补充可以为一个或一批异步任务生成Future的方法;
    • 提供了管控线程池的方法,比如停止线程池的运行。
  • AbstractExecutorService

    AbstractExecutorService则是上层的抽象类,将执行任务的流程串联了起来,保证下层的实现只需关注一个执行任务的方法即可。

  • ThradPoolExecutor

    最下层的实现类ThreadPoolExecutor实现最复杂的运行部分,ThreadPoolExecutor将会一方面维护自身的生命周期,另一方面同时管理线程和任务,使两者良好的结合从而执行并行任务。

ThreadPoolExecutor是如何运行,如何同时维护线程和执行任务的呢?

ThreadPoolExecutor运行机制如下图所示:

image.png

线程池在内部实际上构建了一个「生产者消费者模型」,将线程和任务两者解耦,并不直接关联,从而良好的缓冲任务,复用线程。

线程池的运行主要分成两部分:任务管理,线程管理

任务管理部分充当生产者的角色,线程管理则作为消费者,当任务提交后,线程池会判断该任务后续的流转:

  1. 线程池首先判断当前运行的线程数是否少于corePoolSize。如果是,则创建一个新的工作线程来执行任务。(当ThreadPoolExecutor创建新线程时,通过CAS来更新线程池的状态ctl)如果都在执行任务,则进入2
  2. 判断BlockingQueue是否已经满了,如果没有满,则将线程放入BlockingQueue。否则进入3
  3. 如果创建一个新的工作线程将使当前运行的线程数量超过maximumPoolSize,则交给RejectedExecutionHandler来处理任务。

上述参数我们都可以在ThreadPoolExecutor的构造函数中看到:

  • ThreadPoolExecutor构造函数及参数解释:

    public ThreadPoolExecutor(int corePoolSize,
                                  int maximumPoolSize,
                                  long keepAliveTime,
                                  TimeUnit unit,
                                  BlockingQueue<Runnable> workQueue,
                                  RejectedExecutionHandler handler)
    复制代码
    • corePoolSize

      线程池中的核心线程数,当提交一个任务时,线程池创建一个新线程执行任务,直到当前线程数等于corePoolSize。即使有其他空闲线程能够执行新来的任务, 也会继续创建线程;

      如果当前线程数为corePoolSize,继续提交的任务被保存到阻塞队列中,等待被执行;

      如果执行了线程池的prestartAllCoreThreads()方法,线程池会提前创建并启动所有核心线程。

    • maximumPoolSize

      线程池中允许的最大线程数。如果当前阻塞队列满了,且继续提交任务,则创建新的线程执行任务,前提是当前线程数小于maximumPoolSize

      当阻塞队列是无界队列, 则maximumPoolSize则不起作用, 因为无法提交至核心线程池的线程会一直持续地放入workQueue

    • keepAliveTime

      线程空闲时的存活时间,即当线程没有任务执行时,该线程继续存活的时间;默认情况下,该参数只在线程数大于corePoolSize时才有用, 超过这个时间的空闲线程将被终止;

    • unit

      keepAliveTime的单位

    • workQueue

      用来保存等待被执行的任务的阻塞队列。在JDK中提供了如下阻塞队列:

      • ArrayBlockingQueue: 基于数组结构的有界阻塞队列,按FIFO排序任务;
      • LinkedBlockingQueue: 基于链表结构的阻塞队列,按FIFO排序任务,吞吐量通常要高于ArrayBlockingQueue
      • SynchronousQueue: 一个不存储元素的阻塞队列,每个插入操作必须等到另一个线程调用移除操作,否则插入操作一直处于阻塞状态,吞吐量通常要高于LinkedBlockingQueue
      • PriorityBlockingQueue: 具有优先级的无界阻塞队列;

      LinkedBlockingQueueArrayBlockingQueue在插入删除节点性能方面更优,但是二者在put(), take()任务的时均需要加锁,SynchronousQueue使用无锁算法,根据节点的状态判断执行,而不需要用到锁,其核心是Transfer.transfer()。)

    • handler

      线程池的饱和策略,当阻塞队列满了,且没有空闲的工作线程,如果继续提交任务,必须采取一种策略处理该任务,线程池提供了4种策略:

      • AbortPolicy: 直接抛出异常,默认策略;
      • CallerRunsPolicy: 用调用者所在的线程来执行任务;
      • DiscardOldestPolicy: 丢弃阻塞队列中靠最前的任务,并执行当前任务;
      • DiscardPolicy: 直接丢弃任务;

      当然也可以根据应用场景实现RejectedExecutionHandler接口,自定义饱和策略,如记录日志或持久化存储不能处理的任务。

    • threadFactory

      创建线程的工厂,通过自定义的线程工厂可以给每个新建的线程设置一个具有识别度的线程名。默认为DefaultThreadFactory

Java提供的四种线程池

JDK的线程池实现

以下的方法都是Executors类中的静态工厂方法,用于创建我们需要的线程池:

  • newFixedThreadPool

    • 什么是newFixedThreadPool?

      固定大小的线程池,里面都是核心线程。线程池的线程数量达corePoolSize后,即使线程池没有可执行任务时,也不会释放线程。

      创建它的方法如下:

      public static ExecutorService newFixedThreadPool(int nThreads) {
          return new ThreadPoolExecutor(nThreads, nThreads,
                                      0L, TimeUnit.MILLISECONDS,
                                      new LinkedBlockingQueue<Runnable>());
      }
      复制代码

      FixedThreadPool的工作队列为无界队列LinkedBlockingQueue(队列容量为Integer.MAX_VALUE), 这会导致以下问题:

      • 线程池里的线程数量不超过corePoolSize,这导致了maximumPoolSizekeepAliveTime将会是个无用参数
      • 由于使用了无界队列, 所以FixedThreadPool永远不会拒绝,即饱和策略失效
    • 测试及解释

      public static void main(String[] args) {
          ExecutorService executorService = Executors.newFixedThreadPool(2);
          executorService.execute(()->{
              log.debug("1");
          });
      ​
          executorService.execute(()->{
              log.debug("2");
          });
      ​
          executorService.execute(()->{
              log.debug("3");
          });
      }
      复制代码

      运行结果如下:

      image.png

      我们注意到执行这三个任务的线程在任务结束以后还是没有停止,这说明线程池的线程需要手动调用结束方法,这点我们后面再说。

      我们注意到线程名「pool-1-thread-*」这里对应的源码如下:

      image.png

      通过这个DefaultThreadFactory工厂给我们的线程起了名字,使用的是原子整数自增编号。

      既然看到了这个线程工厂的源码,那么我们也可以给它自定义线程工厂:

      @Test
      public void testDIYFixedThreadPool() throws InterruptedException {
          ExecutorService executorService = Executors.newFixedThreadPool(2, new ThreadFactory() {
              private AtomicInteger t = new AtomicInteger(1);
      ​
              @Override
              public Thread newThread(Runnable r) {
                  return new Thread(r, "mypool_t" + t.getAndIncrement());
              }
          });
      ​
          executorService.execute(()->{
              log.debug("1");
          });
      ​
          executorService.execute(()->{
              log.debug("2");
          });
      ​
          executorService.execute(()->{
              log.debug("3");
          });
      ​
          Thread.sleep(1000L);
      }
      复制代码

      运行结果如下:

      image.png

  • newCachedThreadPool

    • 什么是newCachedThreadPool

      这个是一个带缓冲的线程池。核心线程为0,创建出来的线程都是救急线程,每个线程存活时间为60s。

      创建它的方法如下:

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

      线程池的线程数可达到Integer.MAX_VALUE,即2147483647

      内部使用SynchronousQueue作为阻塞队列; 和newFixedThreadPool创建的线程池不同,newCachedThreadPool没有任务执行时,当线程的空闲时间超过keepAliveTime,会自动释放线程资源,当提交新任务时,如果没有空闲线程,则创建新线程执行任务,会导致一定的系统开销

    • 适用场景

      适合任务数比较密集,但每个任务执行时间较短的情况。

    • 测试及解释

      我们这里主要测试SynchronousQueue

      @Test
      public void testCachedThreadPool() throws InterruptedException {
          SynchronousQueue<Integer> integers = new SynchronousQueue<>();
          new Thread(()->{
              try{
                  log.debug("{} 正入队",1);
                  integers.put(1);
                  log.debug("{} 已入队",1);
                  log.debug("{} 正入队",2);
                  integers.put(2);
                  log.debug("{} 已入队",2);
              }catch (InterruptedException e){
                  e.printStackTrace();
              }
          },"t1").start();
      ​
          Thread.sleep(500L);
      ​
          new Thread(()->{
              try{
                  log.debug("取出 {}",1);
                  integers.take();
              }catch (InterruptedException e){
                  e.printStackTrace();
              }
          },"t2").start();
          new Thread(()->{
              try{
                  log.debug("取出 {}",2);
                  integers.take();
              }catch (InterruptedException e){
                  e.printStackTrace();
              }
          },"t3").start();
      ​
          Thread.sleep(5000L);
      }
      复制代码

      运行结果:

      image.png

      我们这里发现一个对象放进去后它就阻塞住,等待一个线程来取,否则就不会放下一个。

      这个特性就非常适合newCachedThreadPool,这个工厂线程池表现出来的特点就是线程数根据任务量不断增长,没有上限,等任务执行完毕,空闲1分钟后释放掉线程。

      所以说这个线程池适合任务数量大,但是每个任务执行时间比较短。

      我们试着使用这个线程池执行100000个任务:

      @Test
      public void testCachedThreadPool() throws InterruptedException {
          ExecutorService executorService = Executors.newCachedThreadPool();
          for (int i = 0; i < 100000; i++) {
              int count = i;
              executorService.execute(()->{
                  log.debug(String.valueOf(count));
              });
          }
          executorService.shutdown();
          executorService.awaitTermination(Long.MAX_VALUE, TimeUnit.MINUTES);
      }
      ​
      复制代码

      运行结果:

      image.png

      这里发现它创建的线程数量至少达到了12824个,也验证了我们上面说的。

  • newSingleThreadExecutor

    • 什么是newSingleThreadExecutor

      初始化的线程池中只有一个线程,如果该线程异常结束,会重新创建一个新的线程继续执行任务,唯一的线程可以保证所提交任务的顺序执行。

      创建该线程池的方法如下:

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

      由于适用了无界队列,所以SingleThreadPool永远不会拒绝,即饱和策略失效。

    • 适用场景

      希望多个任务排队执行,线程数固定为1,任务数多于1时,会放入无界队列排队(它保证了阻塞队列重点额任务都是串行的。不会出现并发并行的情况)。任务执行完毕,这唯一的线程也不会被释放。

      比如说:分布式情况下,保证双写一致的情况下,需要将相同的业务留给同一台服务器或者线程来处理。

      主线程执行正常流程,线程池执行异步流程,比如短信通知。

    • 和我们自己创建一个单线程执行的区别?和newFixedThreadPool(1)又有什么区别呢?

      • 自己创建一个单线程串行执行任务,如果任务执行失败而终止那么没有任何补救措施,而线程池还会新建一个线程,保证池的正常工作

      • Executors.newSingleThreadExecutor()线程个数始终为1,不能修改

        FinalizableDelegatedExecutorService应用的是装饰器模式,只对外暴露了ExecutorService接口,因此不能调用ThreadPoolExecutor中特有的方法。

      • Executors.newFixedThreadPool(1)初始时为1,以后还可以修改

        对外暴露的是ThreadPoolExecutor对象,可以强转后调用setCorePoolSize等方法进行修改。

    • 测试及解释

      这里我们故意让一个任务有异常:

      @Test
      public void testSingleThreadExecutor() throws InterruptedException {
          ExecutorService executorService = Executors.newSingleThreadExecutor();
          executorService.execute(()->{
              log.debug("1");
              int i=1/0;
          });
      ​
          executorService.execute(()->{
              log.debug("2");
          });
      ​
          executorService.execute(()->{
              log.debug("3");
          });
          executorService.shutdown();
          executorService.awaitTermination(Long.MAX_VALUE,TimeUnit.MINUTES);
      }
      复制代码

      运行结果:

      image.png

  • ScheduledThreadPoolExecutor

    什么是ScheduledThreadPoolExecutor

    ScheduledThreadPoolExecutor继承自 ThreadPoolExecutor,为任务提供延迟或周期执行,属于线程池的一种。

    它和上面不同的就是它是一个独立的类,而前面的都是工厂方法。

    由于需要理解这个类得先理解FutureTask类,所以我们把这个类放到后面的博客中介绍。

ThreadPoolExecutor 源码详解

关键属性

//int是32位的,这里把int的高3位拿来充当线程池状态的标志位,后29位拿来充当当前运行worker的数量
private final AtomicInteger ctl = new AtomicInteger(ctlOf(RUNNING, 0));
//存放任务的阻塞队列
private final BlockingQueue<Runnable> workQueue;
//worker的集合,用set来存放
private final HashSet<Worker> workers = new HashSet<Worker>();
//历史达到的worker数最大值
private int largestPoolSize;
//当队列满了并且worker的数量达到maxSize的时候,执行具体的拒绝策略
private volatile RejectedExecutionHandler handler;
//超出coreSize的worker的生存时间
private volatile long keepAliveTime;
//常驻worker的数量
private volatile int corePoolSize;
//最大worker的数量,一般当workQueue满了才会用到这个参数
private volatile int maximumPoolSize;
复制代码
  • workQueue

    存放任务的阻塞队列

  • workers

    worker的集合

  • largestPoolSize

    历史达到的worker数最大值

  • handler

    当队列满了并且worker的数量达到maxSize的时候,执行具体的拒绝策略

  • keepAliveTime

    超出coreSizeworker的生存时间

  • corePoolSize

    常驻worker的数量

  • maximumPoolSize

    最大worker的数量,一般当workQueue满了才会用到这个参数。

生命周期

内部状态

private final AtomicInteger ctl = new AtomicInteger(ctlOf(RUNNING, 0));
private static final int COUNT_BITS = Integer.SIZE - 3;
private static final int CAPACITY   = (1 << COUNT_BITS) - 1;
​
// runState存储在高阶位中
private static final int RUNNING    = -1 << COUNT_BITS;
private static final int SHUTDOWN   =  0 << COUNT_BITS;
private static final int STOP       =  1 << COUNT_BITS;
private static final int TIDYING    =  2 << COUNT_BITS;
private static final int TERMINATED =  3 << COUNT_BITS;
​
// 打包和拆箱ctl
private static int runStateOf(int c)     { return c & ~CAPACITY; }
private static int workerCountOf(int c)  { return c & CAPACITY; }
private static int ctlOf(int rs, int wc) { return rs | wc; }
复制代码
  • ctl

    线程池运行的状态,并不是用户显式设置的,而是伴随着线程池的运行,由内部来维护。线程池内部使用一个变量维护两个值:运行状态(runState)和线程数量 (workerCount)。具体实现就是用了这个ctl来同时维护这两个值的。

    ctl可以利用低29为表示线程池中的线程数,通过高3位表示线程池的运行状态。

    COUNT_BITS在int是32位的时候是29,于是左移COUNT_BITS位就是要当前数的前三位)

  • RUNNING: -1 << COUNT_BITS

    即高3位为111,该状态的线程池会接收新任务,并处理阻塞队列中的任务;

  • SHUTDOWN: 0 << COUNT_BITS

    即高3位为000,该状态的线程池不会接收新任务,但会处理阻塞队列中的任务;

  • STOP : 1 << COUNT_BITS

    即高3位为001,该状态的线程不会接收新任务,也不会处理阻塞队列中的任务,而且会中断正在运行的任务;

  • TIDYING : 2 << COUNT_BITS

    即高3位为010, 所有的任务都已经终止;workerCount(有效线程数)为0

  • TERMINATED: 3 << COUNT_BITS

    即高3位为011, terminated()方法已经执行完成

获取生命周期状态、获取线程池数量的计算方法

  • runStateOf(int c)

    计算当前运行状态

  • workerCountOf(int c)

    计算当前线程数量

  • ctlOf(int rs, int wc)

    通过状态和线程数生成ctl

状态(生命周期)之间相互转换如下图所示:

image.png

任务执行

线程池中任务的执行链

image.png

线程池的工作线程通过Woker类实现,在ReentrantLock锁的保证下,把Woker实例插入到HashSet后,并启动Woker中的线程。

Worker本身是ThreadPoolExecutor的静态内部类,构造方法如下:

Worker(Runnable firstTask) {
    setState(-1); // inhibit interrupts until runWorker
    this.firstTask = firstTask;
    this.thread = getThreadFactory().newThread(this);
}
复制代码

Woker类的构造方法实现可以发现:

线程工厂在创建线程thread时,将Woker实例本身this作为参数传入,当执行start方法启动线程thread时,本质是执行了WorkerrunWorker方法。 对应Worker中的run方法如下:

public void run() {
    runWorker(this);
}
复制代码

firstTask执行完成之后,通过getTask方法从阻塞队列中获取等待的任务,如果队列中没有任务,getTask方法会被阻塞并挂起,不会占用CPU资源;

image.png

接着我们顺着执行链来依次介绍这几个方法。

execute方法

exeucte方法及源码

ThreadPoolExecutor.execute(task)实现了Executor.execute(task)

源码如下:

public void execute(Runnable command) {
    if (command == null)
        throw new NullPointerException();
    int c = ctl.get();
    if (workerCountOf(c) < corePoolSize) {  
    //workerCountOf获取线程池的当前线程数;小于corePoolSize,执行addWorker创建新线程执行command任务
       if (addWorker(command, true))
            return;
        c = ctl.get();
    }
    // double check: c, recheck
    // 线程池处于RUNNING状态,把提交的任务成功放入阻塞队列中
    if (isRunning(c) && workQueue.offer(command)) {
        int recheck = ctl.get();
        // recheck and if necessary 回滚到入队操作前,即倘若线程池shutdown状态,就remove(command)
        // 如果线程池没有RUNNING,成功从阻塞队列中删除任务,执行reject方法处理任务
        if (! isRunning(recheck) && remove(command))
            reject(command);
        // 线程池处于running状态,但是没有线程,则创建线程
        else if (workerCountOf(recheck) == 0)
            addWorker(null, false);
    }
    // 往线程池中创建新的线程失败,则reject任务
    else if (!addWorker(command, false))
        reject(command);
}
复制代码

所有任务的调度都是由execute方法完成的,这部分完成的工作是:

检查现在线程池的运行状态、运行线程数、运行策略,决定接下来执行的流程,是直接申请线程执行,或是缓冲到队列中执行,亦或是直接拒绝该任务。

其执行过程如下:

image.png

  1. 首先检测线程池运行状态,如果不是RUNNING,则直接拒绝,线程池要保证在RUNNING的状态下执行任务。
  2. 如果workerCount < corePoolSize,则创建并启动一个线程来执行新提交的任务。
  3. 如果workerCount >= corePoolSize,且线程池内的阻塞队列未满,则将任务添加到该阻塞队列中。
  4. 如果workerCount >= corePoolSize && workerCount < maximumPoolSize,且线程池内的阻塞队列已满,则创建并启动一个线程来执行新提交的任务。
  5. 如果workerCount >= maximumPoolSize,并且线程池内的阻塞队列已满, 则根据拒绝策略来处理该任务, 默认的处理方式是直接抛异常。
  • 这里为什么需要double check线程池的状态?

    在多线程环境下,线程池的状态时刻在变化,而ctl.get()是非原子操作,很可能刚获取了线程池状态后线程池状态就噶癌变了。判断是否将command加入workqueue是线程池之前的状态。如果没有double check,万一线程池处于非RUNNING状态(多线程环境下很可能发生),那么command永远不会执行。

addWorker方法

Worker线程管理和Worker线程

线程池为了掌握线程的状态并维护线程的生命周期,设计了**线程池内的工作线程Worker**。我们来看一下它的部分代码:

private final class Worker extends AbstractQueuedSynchronizer implements Runnable{
    final Thread thread;//Worker持有的线程
    Runnable firstTask;//初始化的任务,可以为null
}
复制代码

Worker这个工作线程,实现了Runnable接口,并持有一个线程thread,一个初始化的任务firstTask

  • thread是在调用构造方法时通过ThreadFactory来创建的线程,可以用来执行任务;
  • firstTask用它来保存传入的第一个任务,这个任务可以有也可以为null。

    如果这个值是非空的,那么线程就会在启动初期立即执行这个任务,也就对应核心线程创建时的情况;

    如果这个值是null,那么就需要创建一个线程去执行任务列表(workQueue)中的任务,也就是非核心线程的创建。

Worker执行任务的模型如下图所示:

image.png

addWorker方法有什么用?

addWorkerWorker线程管理的增加线程部分。

源码如下:

private final ReentrantLock mainLock = new ReentrantLock();
private boolean addWorker(Runnable firstTask, boolean core) {
    // CAS更新线程池数量
    retry:
    for (;;) {
        int c = ctl.get();
        int rs = runStateOf(c);
​
        // Check if queue empty only if necessary.
        if (rs >= SHUTDOWN &&
            ! (rs == SHUTDOWN &&
                firstTask == null &&
                ! workQueue.isEmpty()))
            return false;
​
        for (;;) {
            int wc = workerCountOf(c);
            if (wc >= CAPACITY ||
                wc >= (core ? corePoolSize : maximumPoolSize))
                return false;
            if (compareAndIncrementWorkerCount(c))
                break retry;
            c = ctl.get();  // Re-read ctl
            if (runStateOf(c) != rs)
                continue retry;
            // else CAS failed due to workerCount change; retry inner loop
        }
    }
​
    boolean workerStarted = false;
    boolean workerAdded = false;
    Worker w = null;
    try {
        w = new Worker(firstTask);
        final Thread t = w.thread;
        if (t != null) {
            // 线程池重入锁
            final ReentrantLock mainLock = this.mainLock;
            mainLock.lock();
            try {
                // Recheck while holding lock.
                // Back out on ThreadFactory failure or if
                // shut down before lock acquired.
                int rs = runStateOf(ctl.get());
​
                if (rs < SHUTDOWN ||
                    (rs == SHUTDOWN && firstTask == null)) {
                    if (t.isAlive()) // precheck that t is startable
                        throw new IllegalThreadStateException();
                    workers.add(w);
                    int s = workers.size();
                    if (s > largestPoolSize)
                        largestPoolSize = s;
                    workerAdded = true;
                }
            } finally {
                mainLock.unlock();
            }
            if (workerAdded) {
                t.start();  // 线程启动,执行任务(Worker.thread(firstTask).start());
                workerStarted = true;
            }
        }
    } finally {
        if (! workerStarted)
            addWorkerFailed(w);
    }
    return workerStarted;
}
复制代码

addWorker主要负责创建新的线程并执行任务,线程池创建新线程执行任务时需要获取全局锁。

addWorker方法有两个参数:firstTaskcore

  • firstTask参数用于指定新增的线程执行的第一个任务,该参数可以为空;
  • core参数为true表示在新增线程时会判断当前活动线程数是否少于corePoolSize,false表示新增线程前需要判断当前活动线程数是否少于maximumPoolSize

其执行流程如下图所示:

image.png

runWorker方法

Worker的run方法实际调用的是runWorker方法

这里我们主要看到Worker的继承关系和它的构造方法:

private final class Worker extends AbstractQueuedSynchronizer implements Runnable{
    Worker(Runnable firstTask) {
        setState(-1); // inhibit interrupts until runWorker
        this.firstTask = firstTask;
        this.thread = getThreadFactory().newThread(this); // 创建线程
    }
    /** Delegates main run loop to outer runWorker  */
    public void run() {
        runWorker(this);
    }
    // ...
}
复制代码
  • 继承了AQS类,可以方便的实现工作线程的中止操作;
  • 实现了Runnable接口,可以将自身作为一个任务在工作线程中执行;
  • 当前提交的任务firstTask作为参数传入Worker的构造方法;

一些属性还有构造方法:

//运行的线程,前面addWorker方法中就是直接通过启动这个线程来启动这个worker
final Thread thread;
//当一个worker刚创建的时候,就先尝试执行这个任务
Runnable firstTask;
//记录完成任务的数量
volatile long completedTasks;
​
Worker(Runnable firstTask) {
    setState(-1); // inhibit interrupts until runWorker
    this.firstTask = firstTask;
    //创建一个Thread,将自己设置给他,后面这个thread启动的时候,也就是执行worker的run方法
    this.thread = getThreadFactory().newThread(this);
}   
复制代码

runWorker方法

runWorker方法是线程池的核心:

final void runWorker(Worker w) {
    Thread wt = Thread.currentThread();
    Runnable task = w.firstTask;
    w.firstTask = null;
    // 通过unlock方法释放锁
    w.unlock();
    boolean completedAbruptly = true;
    try {
        // 先执行firstTask,再从workerQueue中取task(getTask())
        while (task != null || (task = getTask()) != null) {
            // 进行加锁操作,保证thread不被其他线程中断(除非线程池被中断)
            w.lock();
            // 检查线程池状态,倘若线程池处于中断状态,当前线程将中断。
            if ((runStateAtLeast(ctl.get(), STOP) ||
                    (Thread.interrupted() &&
                    runStateAtLeast(ctl.get(), STOP))) &&
                !wt.isInterrupted())
                wt.interrupt();
            try {
                // 执行 beforeExecute
                beforeExecute(wt, task);
                Throwable thrown = null;
                try {
                    task.run();
                } catch (RuntimeException x) {
                    thrown = x; throw x;
                } catch (Error x) {
                    thrown = x; throw x;
                } catch (Throwable x) {
                    thrown = x; throw new Error(x);
                } finally {
                    // 执行afterExecute方法
                    afterExecute(task, thrown);
                }
            } finally {
                task = null;
                w.completedTasks++;
                // 解锁操作
                w.unlock();
            }
        }
        completedAbruptly = false;
    } finally {
        processWorkerExit(w, completedAbruptly);
    }
}
复制代码

线程启动之后,通过unlock方法释放锁,设置AQS的state为0,表示运行可中断;Worker执行firstTask或从workQueue中获取任务

getTask方法

getTask方法

下面来看一下getTask()方法,这里面涉及到keepAliveTime的使用,从这个方法我们可以看出线程池是怎么让超过corePoolSize的那部分worker销毁的。

private Runnable getTask() {
    boolean timedOut = false; // Did the last poll() time out?
​
    // 进入自旋状态
    for (;;) {
        int c = ctl.get();
        int rs = runStateOf(c);
​
        // 判断当前的线程池状态是否为SHUTDOWN状态或者STOP状态,或者队列为空,如果满足的话,那么Worker总数减一,并且返回空
        if (rs >= SHUTDOWN && (rs >= STOP || workQueue.isEmpty())) {
            decrementWorkerCount();
            return null;
        }
​
        int wc = workerCountOf(c);
​
        // 当获取到的Task为空的话,那么timeOut标志位被设置成true
        boolean timed = allowCoreThreadTimeOut || wc > corePoolSize;
​
        // 当WorkreCount小于maximumPoolSize并且没有超时的话,那么就会进入获取Task的阶段,否则则进入销毁Worker的步骤中,如果减少worker失败的话,那么就会重新进入循环,而如果当前状态和之前状态不一致的话,那么就重新回到retry的地方,重新判断状态
        if ((wc > maximumPoolSize || (timed && timedOut))
            && (wc > 1 || workQueue.isEmpty())) {
            if (compareAndDecrementWorkerCount(c))
                return null;
            continue;
        }
​
        try {
            // 在获取Task的时候,会判断timed,如果timed为true的话,那么就会从Queue中等到keepAliveTime的时长,如果时间段中有新的任务的话,那么就会返回,否则的话,直接从队列中获取。把获取到的task返回
            Runnable r = timed ?
                workQueue.poll(keepAliveTime, TimeUnit.NANOSECONDS) :
                workQueue.take();
            // 如果获取到的runnable为空的话,那么把timedOut设置成true
            if (r != null)
                return r;
            timedOut = true;
        } catch (InterruptedException retry) {
            timedOut = false;
        }
    }
}
复制代码
  • 进入自旋状态
  • 判断当前的线程池状态是否为SHUTDOWN状态或者STOP状态,或者队列为空,如果满足的话,那么Worker总数减一,并且返回空
  • 当获取到的Task为空的话,那么timeOut标志位被设置成true
  • WorkerCount小于maximumPoolSize并且没有超时的话,那么就会进入获取Task的阶段,否则则进入销毁Worker的步骤中,如果减少worker失败的话,那么就会重新进入循环,而如果当前状态和之前状态不一致的话,那么就重新回到retry的地方,重新判断状态
  • 而在获取Task的时候,会判断timed,如果timedtrue的话,那么就会从Queue中等到keepAliveTime的时长,如果时间段中有新的任务的话,那么就会返回,否则的话,直接从队列中获取。把获取到的task返回
  • 如果获取到的runnable为空的话,那么把timedOut设置成true

注意这里一段代码是keepAliveTime起作用的关键代码片段:

boolean timed = allowCoreThreadTimeOut || wc > corePoolSize;
Runnable r = timed ?
                workQueue.poll(keepAliveTime, TimeUnit.NANOSECONDS) :
                workQueue.take();
复制代码

allowCoreThreadTimeOutfalse,线程即使空闲也不会被销毁;倘若为ture,在keepAliveTime内仍空闲则会被销毁。

如果线程允许空闲等待而不被销毁timed == falseworkQueue.take任务(如果阻塞队列为空,当前线程会被挂起等待;当队列中有任务加入时,线程被唤醒,take方法返回任务,并执行)

如果线程不允许无休止空闲timed == true, workQueue.poll任务(如果在keepAliveTime时间内,阻塞队列还是没有任务,则返回null)

任务提交

任务提交及执行流程

image.png

  • submit任务,等待线程池execute
  • 执行FutureTask类的get方法时,会把主线程封装成WaitNode节点并保存在waiters链表中, 并阻塞等待运行结果;
  • FutureTask任务执行完成后,通过UNSAFE设置waiters相应的waitNode为null,并通过LockSupportunpark方法唤醒主线程;

这里我们编写测试方法来测试FutureTask

@Test
public void testFutureTask(){
    ExecutorService executorService = Executors.newCachedThreadPool();
    Future<String> future = executorService.submit(new Callable<String>() {
        @Override
        public String call() throws Exception {
            try {
                TimeUnit.SECONDS.sleep(2);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            return "future result";
        }
    });
    try {
        String result = future.get();
        System.out.println(result);
    } catch (Exception e) {
        e.printStackTrace();
    }
}
复制代码

运行结果:

image.png

在实际业务场景中,FutureCallable基本是成对出现的,Callable负责产生结果,Future负责获取结果。

  • Callable接口类似于Runnable,只是Runnable没有返回值。
  • Callable任务除了返回正常结果之外,如果发生异常,该异常也会被返回,即Future可以拿到异步执行任务各种结果;
  • Future.get方法会导致主线程阻塞,直到Callable任务执行完成。

submit方法

submit方法在ExecutorService接口中的定义

<T> Future<T> submit(Callable<T> task);
​
<T> Future<T> submit(Runnable task, T result);
​
Future<?> submit(Runnable task);
复制代码

submit方法在AbstractExecutorService中的实现

// submit方法在AbstractExecutorService中的实现
public Future<?> submit(Runnable task) {
    if (task == null) throw new NullPointerException();
    // 通过submit方法提交的Callable任务会被封装成了一个FutureTask对象。
    RunnableFuture<Void> ftask = newTaskFor(task, null);
    execute(ftask);
    return ftask;
}
复制代码

AbstractExecutorService.submit()实现了ExecutorService.submit() 可以获取执行完的返回值

通过submit方法提交的Callable任务会被封装成了一个FutureTask对象。通过Executor.execute方法提交FutureTask到线程池中等待被执行,最终执行的是FutureTaskrun方法;

FutureTask对象

内部状态

private volatile int state;
private static final int NEW          = 0;
private static final int COMPLETING   = 1;
private static final int NORMAL       = 2;
private static final int EXCEPTIONAL  = 3;
private static final int CANCELLED    = 4;
private static final int INTERRUPTING = 5;
private static final int INTERRUPTED  = 6;
复制代码

内部状态的修改通过sun.misc.Unsafe修改

get方法

public V get() throws InterruptedException, ExecutionException {
    int s = state;
    if (s <= COMPLETING)
        s = awaitDone(false, 0L);
    return report(s);
} 
复制代码

内部通过awaitDone方法对主线程进行阻塞,具体实现如下:

private int awaitDone(boolean timed, long nanos)
    throws InterruptedException {
    final long deadline = timed ? System.nanoTime() + nanos : 0L;
    WaitNode q = null;
    boolean queued = false;
    for (;;) {
        if (Thread.interrupted()) {
            removeWaiter(q);
            throw new InterruptedException();
        }
​
        int s = state;
        if (s > COMPLETING) {
            if (q != null)
                q.thread = null;
            return s;
        }
        else if (s == COMPLETING) // cannot time out yet
            Thread.yield();
        else if (q == null)
            q = new WaitNode();
        else if (!queued)
            queued = UNSAFE.compareAndSwapObject(this, waitersOffset,q.next = waiters, q);
        else if (timed) {
            nanos = deadline - System.nanoTime();
            if (nanos <= 0L) {
                removeWaiter(q);
                return state;
            }
            LockSupport.parkNanos(this, nanos);
        }
        else
            LockSupport.park(this);
    }
}
复制代码
  • 如果主线程被中断,则抛出中断异常;
  • 判断FutureTask当前的state,如果大于COMPLETING,说明任务已经执行完成,则直接返回;
  • 如果当前state等于COMPLETING,说明任务已经执行完,这时主线程只需通过yield方法让出CPU资源,等待state变成NORMAL
  • 通过WaitNode类封装当前线程,并通过UNSAFE添加到waiters链表;
  • 最终通过LockSupportparkparkNanos挂起线程;

run方法

public void run() {
    if (state != NEW || !UNSAFE.compareAndSwapObject(this, runnerOffset, null, Thread.currentThread()))
        return;
    try {
        Callable<V> c = callable;
        if (c != null && state == NEW) {
            V result;
            boolean ran;
            try {
                result = c.call();
                ran = true;
            } catch (Throwable ex) {
                result = null;
                ran = false;
                setException(ex);
            }
            if (ran)
                set(result);
        }
    } finally {
        // runner must be non-null until state is settled to
        // prevent concurrent calls to run()
        runner = null;
        // state must be re-read after nulling runner to prevent
        // leaked interrupts
        int s = state;
        if (s >= INTERRUPTING)
            handlePossibleCancellationInterrupt(s);
    }
}
复制代码

FutureTask.run方法是在线程池中被执行的,而非主线程

  • 通过执行Callable任务的call方法;
  • 如果call执行成功,则通过set方法保存结果;
  • 如果call执行有异常,则通过setException保存异常;

小结

本篇我们从线程池出发,讲了线程池的基本实现原理和池化思想。接着我们详细分析了ThreadPoolExecutor包括它的部分源码,这个类是Java实现线程池的核心。

当然线程池的内容相当之多,本篇也只是谈了它的几个核心部分,其余的还是要再扩展学习。

本章参考:

猜你喜欢

转载自juejin.im/post/7177764792730386491