java并发之线程池threadpoolexecutor源码分析学习_kingjack-编程思维

线程池学习

以下所有内容以及源码分析都是基于JDK1.8的,请知悉。

我写博客就真的比较没有顺序了,这可能跟我的学习方式有关,我自己也觉得这样挺不好的,但是没办法说服自己去改变,所以也只能这样想到什么学什么了。

​ 池化技术真的是一门在我看来非常牛逼的技术,因为它做到了在有限资源内实现了资源利用的最大化,这让我想到了一门课程,那就是运筹学,当时在上运筹学的时候就经常做这种类似的问题。

​ 言归正传吧,我接下来会进行一次线程池方面知识点的学习,也会记录下来分享给大家。

线程池的内容当中有涉及到AQS同步器的知识点,如果对AQS同步器知识点感觉有点薄弱,可以去看我的上一篇文章。

线程池的优势

​ 既然说到线程池了,而且大多数的大牛也都会建议我们使用池化技术来管理一些资源,那线程池肯定也是有它的好处的,要不然怎么会那么出名并且让大家使用呢?

​ 我们就来看看它究竟有什么优势?

  • 资源可控性:使用线程池可以避免创建大量线程而导致内存的消耗

  • 提高响应速度:线程池地创建实际上是很消耗时间和性能的,由线程池创建好有任务就运行,提升响应速度。

  • 便于管理:池化技术最突出的一个特点就是可以帮助我们对池子里的资源进行管理。由线程池统一分配和管理。

线程池的创建

​ 我们要用线程池来统一分配和管理我们的线程,那首先我们要创建一个线程池出来,还是有很多大牛已经帮我们写好了很多方面的代码的,Executors的工厂方法就给我们提供了创建多种不同线程池的方法。因为这个类只是一个创建对象的工厂,并没有涉及到很多的具体实现,所以我不会过于详细地去说明。

​ 老规矩,还是直接上代码吧。

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

这里也就举出一个方法的例子来进行之后的讲解吧,我们可以看出,Executors只是个工厂而已,方法也只是来实例化不同的对象,实际上实例化出来的关键类就是ThreadPoolExecutor。现在我们就先来简单地对ThreadPoolExecutor构造函数内的每个参数进行解释一下吧。

  • corePoolSize(核心线程池大小):当提交一个任务到线程池时,线程池会创建一个线程来执行任务,即使其他空闲的基本线程能够执行新任务也会创建线程,当任务数大于核心线程数的时候就不会再创建。在这里要注意一点,线程池刚创建的时候,其中并没有创建任何线程,而是等任务来才去创建线程,除非调用了prestartAllCoreThreads()或者prestartCoreThread()方法 ,这样才会预先创建好corePoolSize个线程或者一个线程。

  • maximumPoolSize(线程池最大线程数):线程池允许创建的最大线程数,如果队列满了,并且已创建的线程数小于最大线程数,则线程池会再创建新的线程执行任务。值得注意的是,如果使用了无界队列,此参数就没有意义了。

  • keepAliveTime(线程活动保持时间):此参数默认在线程数大于corePoolSize的情况下才会起作用, 当线程的空闲时间达到keepAliveTime的时候就会终止,直至线程数目小于corePoolSize。不过如果调用了allowCoreThreadTimeOut方法,则当线程数目小于corePoolSize的时候也会起作用.

  • unit(keelAliveTime的时间单位):keelAliveTime的时间单位,一共有7种,在这里就不列举了。

  • workQueue(阻塞队列):阻塞队列,用来存储等待执行的任务,这个参数也是非常重要的,在这里简单介绍一下几个阻塞队列。

    • ArrayBlockingQueue:这是一个基于数组结构的有界阻塞队列,此队列按照FIFO的原则对元素进行排序。

    • LinkedBlockingQueue:一个基于链表结构的阻塞队列,此队列按照FIFO排序元素,吞吐量通常要高于ArrayBlockingQueue。静态工厂方法Executors.newFixedThreadPool()就是使用了这个队列。

    • SynchronousQueue:一个不存储元素的阻塞队列。每个插入操作必须等到另一个线程调用移除操作,否则插入操作一直处于阻塞状态。吞吐量通常要高于LinkedBlockingQueue,静态工厂方法Executors.newCachedThreadPool()就使用了这个队列。

    • PriorityBlockingQueue:一个具有优先级的无阻塞队列。

  • handler(饱和策略);当线程池和队列都满了,说明线程池已经处于饱和状态了,那么必须采取一种策略来处理还在提交过来的新任务。这个饱和策略默认情况下是AbortPolicy,表示无法处理新任务时抛出异常。共有四种饱和策略提供,当然我们也可以选择自己实现饱和策略。

    • AbortPolicy:直接丢弃并且抛出RejectedExecutionException异常

    • CallerRunsPolicy:只用调用者所在线程来运行任务。

    • DiscardOldestPolicy:丢弃队列里最近的一个任务,并执行当前任务。

    • DiscardPolicy:丢弃任务并且不抛出异常。

线程池的执行流程就用参考资料里的图介绍一下了,具体我们还是通过代码去讲解。

在上面我们简单的讲解了一下Executors这个工厂类里的工厂方法,并且讲述了一下创建线程池的一些参数以及它们的作用,当然上面的讲解并不是很深入,因为想要弄懂的话是需要持续地花时间去看去理解的,而博主自己也还是没有完全弄懂,不过博主的学习方法是先学了个大概,再回头来看看之前的知识点,可能会更加好理解,所以我们接着往下面讲吧。

ThreadPoolExecutor源码分析

​ 在上面我们就发现了,Executors的工厂方法主要就返回了ThreadPoolExecutor对象,至于另一个在这里暂时不讲,也就是说,要学习线程池,其实关键的还是得学会分析ThreadPoolExecutor这个对象里面的源码,我们接下来就会对ThreadPoolExecutor里的关键代码进行分析。

AtomicInteger ctl

ctl是主要的控制状态,是一个复合类型的变量,其中包括了两个概念。

  • workerCount:表示有效的线程数目

  • runState:线程池里线程的运行状态


我们来分析一下跟ctl有关的一些源代码吧,直接上代码

     private final AtomicInteger ctl = new AtomicInteger(ctlOf(RUNNING, 0));

     //用来表示线程池数量的位数,很明显是29,Integer.SIZE=32
     private static final int COUNT_BITS = Integer.SIZE - 3;
     //线程池最大数量,2^29 - 1
     private static final int CAPACITY   = (1 << COUNT_BITS) - 1;

     // runState is stored in the high-order bits
     //我们可以看出有5种runState状态,证明至少需要3位来表示runState状态
     //所以高三位就是表示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;

     // Packing and unpacking 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; }

     //用于存放线程任务的阻塞队列
     private final BlockingQueue<Runnable> workQueue;

     //重入锁
     private final ReentrantLock mainLock = new ReentrantLock();

     //线程池当中的线程集合,只有当拥有mainLock锁的时候,才可以进行访问
     private final HashSet<Worker> workers = new HashSet<Worker>();

     //等待条件支持终止
     private final Condition termination = mainLock.newCondition();

     //创建新线程的线程工厂
     private volatile ThreadFactory threadFactory;

     //饱和策略
     private volatile RejectedExecutionHandler handler;
  1. CAPACITY

    在这里我们讲一下这个线程池最大数量的计算吧,因为这里涉及到源码以及位移之类的操作,我感觉大多数人都还是不太会这个,因为我一开始看的时候也是不太会的。

private static final int CAPACITY   = (1 << COUNT_BITS) - 1;

从代码我们可以看出,是需要1往左移29位,然后再减去1,那个1往左移29位是怎么计算的呢?

     1 << COUNT_BITS
      ​
      1的32位2进制是
      00000000 00000000 00000000 00000001
      ​
      左移29位的话就是
      00100000 00000000 00000000 00000000
      ​
      再进行减一的操作
      000 11111 11111111 11111111 11111111
      ​
      也就是说线程池最大数目就是
      000 11111 11111111 11111111 11111111

2.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;
  • RUNNING

可以接受新任务并且处理已经在阻塞队列的任务
高3位全部是1的话,就是RUNNING状态

-1 << COUNT_BITS

这里是-1往左移29位,稍微有点不一样,-1的话需要我们自己算出补码来
          ​
-1的原码
10000000 00000000 00000000 00000001
          ​
-1的反码,负数的反码是将原码除符号位以外全部取反
11111111 11111111 11111111 11111110
          ​
-1的补码,负数的补码就是将反码+1
11111111 11111111 11111111 11111111
          ​
关键了,往左移29位,所以高3位全是1就是RUNNING状态
111 00000 00000000 00000000 00000000
  • SHUTDOWN

不接受新任务,但是处理已经在阻塞队列的任务
高3位全是0,就是SHUTDOWN状态

0 << COUNT_BITS
          ​
0的表示
00000000 00000000 00000000 00000000
          ​
往左移29位
00000000 00000000 00000000 00000000
  • STOP

不接受新任务,也不处理阻塞队列里的任务,并且会中断正在处理的任务
所以高3位是001,就是STOP状态

1 << COUNT_BITS
          ​
1的表示
00000000 00000000 00000000 00000001
          ​
往左移29位
00100000 00000000 00000000 00000000
  • TIDYING

所有任务都被中止,workerCount是0,线程状态转化为TIDYING并且调用terminated()钩子方法
所以高3位是010,就是TIDYING状态

2 << COUNT_BITS
          ​
2的32位2进制
00000000 00000000 00000000 00000010
          ​
往左移29位
01000000 00000000 00000000 00000000
  • TERMINATED

terminated()钩子方法已经完成
所以高3位是011,就是TERMINATED状态

3 << COUNT_BITS
          ​
3的32位2进制
00000000 00000000 00000000 00000011
          ​
往左移29位
011000000 00000000 00000000 0000000

3.部分方法介绍

  • runStateOf(int c)

实时获取runState的方法

private static int runStateOf(int c)     { return c & ~CAPACITY; }
~CAPACITY
~是按位取反的意思
&是按位与的意思
          ​
而CAPACITY是,高位3个0,低29位都是1,所以是
000 11111 11111111 11111111 11111111
          ​
取反的话就是
111 00000 00000000 00000000 00000000
          ​
传进来的c参数与取反的CAPACITY进行按位与操作
1、低位29个0进行按位与,还是29个0
2、高位3个1,既保持c参数的高3位
既高位保持原样,低29位都是0,这也就获得了线程池的运行状态runState
  • workerCountOf(int c)

获取线程池的当前有效线程数目

private static int workerCountOf(int c)  { return c & CAPACITY; }
CAPACITY的32位2进制是
000 11111 11111111 11111111 11111111
          ​
用入参c跟CAPACITY进行按位与操作
1、低29位都是1,所以保留c的低29位,也就是有效线程数
2、高3位都是0,所以c的高3位也是0
          ​
这样获取出来的便是workerCount的值
  • ctlOf(int rs, int wc)

原子整型变量ctl的初始化方法

//结合这几句代码来看
private static final int RUNNING    = -1 << COUNT_BITS;
private final AtomicInteger ctl = new AtomicInteger(ctlOf(RUNNING, 0));
          ​
private static int ctlOf(int rs, int wc) { return rs | wc; }
RUNNING是
111 00000 00000000 00000000 00000000
          ​
ctlOf是将rs和wc进行按位或的操作
          ​
初始化的时候是将RUNNING和0进行按位或
0的32位2进制是
00000000 00000000 00000000 00000000
          ​
所以初始化的ctl是
111 00000 00000000 00000000 00000000

核心方法源码分析

  1. execute(Runnable command)方法
      public void execute(Runnable command) {
          //需要执行的任务command为空,抛出空指针异常
          if (command == null)  // 1
              throw new NullPointerException();

          /*
          *执行的流程实际上分为三步
          *1、如果运行的线程小于corePoolSize,以用户给定的Runable对象新开一个线程去执行
          *  并且执行addWorker方法会以原子性操作去检查runState和workerCount,以防止当返回false的
          *  时候添加了不应该添加的线程
          *2、 如果任务能够成功添加到队列当中,我们仍需要对添加的线程进行双重检查,有可能添加的线程在前
          *  一次检查时已经死亡,又或者在进入该方法的时候线程池关闭了。所以我们需要复查状态,并有有必
          *  要的话需要在停止时回滚入列操作,或者在没有线程的时候新开一个线程
          *3、如果任务无法入列,那我们需要尝试新增一个线程,如果新建线程失败了,我们就知道线程可能关闭了
          *  或者饱和了,就需要拒绝这个任务
          *
          */

          //获取线程池的控制状态
          int c = ctl.get();  // 2

          //通过workCountOf方法算workerCount值,小于corePoolSize
          if (workerCountOf(c) < corePoolSize) {
              //添加任务到worker集合当中
      if (addWorker(command, true)) 
                  return;  //成功返回
              //失败的话再次获取线程池的控制状态
              c = ctl.get();
          }

          /*
          *判断线程池是否正处于RUNNING状态
          *是的话添加Runnable对象到workQueue队列当中
          */
          if (isRunning(c) && workQueue.offer(command)) {  // 3

              //再次获取线程池的状态
              int recheck = ctl.get();

              //再次检查状态
              //线程池不处于RUNNING状态,将任务从workQueue队列中移除
              if (! isRunning(recheck) && remove(command))
                  //拒绝任务
                  reject(command);
              //workerCount等于0
              else if (workerCountOf(recheck) == 0)  // 4
                  //添加worker
                  addWorker(null, false);
          }
          //加入阻塞队列失败,则尝试以线程池最大线程数新开线程去执行该任务
      else if (!addWorker(command, false))  // 5 
              //执行失败则拒绝任务
              reject(command);
      }

我们来说一下上面这个代码的流程:

1、首先判断任务是否为空,空则抛出空指针异常
2、不为空则获取线程池控制状态,判断小于corePoolSize,添加到worker集合当中执行,

  • 如成功,则返回
  • 失败的话再接着获取线程池控制状态,因为只有状态变了才会失败,所以重新获取
    3、判断线程池是否处于运行状态,是的话则添加command到阻塞队列,加入时也会再次获取状态并且检测
    ​ 状态是否不处于运行状态,不处于的话则将command从阻塞队列移除,并且拒绝任务
    4、如果线程池里没有了线程,则创建新的线程去执行获取阻塞队列的任务执行
    5、如果以上都没执行成功,则需要开启最大线程池里的线程来执行任务,失败的话就丢弃

有时候再多的文字也不如一个流程图来的明白,所以还是画了个execute的流程图给大家方便理解。

2.addWorker(Runnable firstTask, boolean core)

      private boolean addWorker(Runnable firstTask, boolean core) {
          //外部循环标记
          retry:
          //外层死循环
          for (;;) {
              //获取线程池控制状态
              int c = ctl.get();
              //获取runState
              int rs = runStateOf(c);
      ​
              // Check if queue empty only if necessary.

              /**
              *1.如果线程池runState至少已经是SHUTDOWN
              *2\. 有一个是false则addWorker失败,看false的情况
              * - runState==SHUTDOWN,即状态已经大于SHUTDOWN了
              * - firstTask为null,即传进来的任务为空,结合上面就是runState是SHUTDOWN,但是
              *  firstTask不为空,代表线程池已经关闭了还在传任务进来
              * - 队列为空,既然任务已经为空,队列为空,就不需要往线程池添加任务了
              */
              if (rs >= SHUTDOWN &&  //runState大于等于SHUTDOWN,初始位RUNNING
                  ! (rs == SHUTDOWN &&  //runState等于SHUTDOWN
                     firstTask == null &&  //firstTask为null
                     ! workQueue.isEmpty()))  //workQueue队列不为空
                  return false;
      ​
              //内层死循环
              for (;;) {
                  //获取线程池的workerCount数量
                  int wc = workerCountOf(c);
                  //如果workerCount超出最大值或者大于corePoolSize/maximumPoolSize
                  //返回false
                  if (wc >= CAPACITY ||
                      wc >= (core ? corePoolSize : maximumPoolSize))
                      return false;
                  //通过CAS操作,使workerCount数量+1,成功则跳出循环,回到retry标记
                  if (compareAndIncrementWorkerCount(c))
                      break retry;

                  //CAS操作失败,再次获取线程池的控制状态
                  c = ctl.get();  // Re-read ctl
                  //如果当前runState不等于刚开始获取的runState,则跳出内层循环,继续外层循环
                  if (runStateOf(c) != rs)
                      continue retry;
                  // else CAS failed due to workerCount change; retry inner loop
                  //CAS由于更改workerCount而失败,继续内层循环
              }
          }
      ​
          //通过以上循环,能执行到这是workerCount成功+1了

          //worker开始标记
          boolean workerStarted = false;
          //worker添加标记
          boolean workerAdded = false;
          //初始化worker为null
          Worker w = null;
          try {
              //初始化一个当前Runnable对象的worker对象
              w = new Worker(firstTask);
              //获取该worker对应的线程
              final Thread t = w.thread;
              //如果线程不为null
              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.
                      //获取锁后再次检查,获取线程池runState
                      int rs = runStateOf(ctl.get());
      ​
                      //当runState小于SHUTDOWN或者runState等于SHUTDOWN并且firstTask为null
                      if (rs < SHUTDOWN ||
                          (rs == SHUTDOWN && firstTask == null)) {

                          //线程已存活
                          if (t.isAlive()) // precheck that t is startable
                              //线程未启动就存活,抛出IllegalThreadStateException异常
                              throw new IllegalThreadStateException();

                          //将worker对象添加到workers集合当中
                          workers.add(w);
                          //获取workers集合的大小
                          int s = workers.size();
                          //如果大小超过largestPoolSize
                          if (s > largestPoolSize)
                              //重新设置largestPoolSize
                              largestPoolSize = s;
                          //标记worker已经被添加
                          workerAdded = true;
                      }
                  } finally {
                      //释放锁
                      mainLock.unlock();
                  }
                  //如果worker添加成功
                  if (workerAdded) {
                      //启动线程
                      t.start();
                      //标记worker已经启动
                      workerStarted = true;
                  }
              }
          } finally {
              //如果worker没有启动成功
              if (! workerStarted)
                  //workerCount-1的操作
                  addWorkerFailed(w);
          }
          //返回worker是否启动的标记
          return workerStarted;
      }

我们也简单说一下这个代码的流程吧,还真的是挺难的,博主写的时候都停了好多次,想砸键盘的说:

1、获取线程池的控制状态,进行判断,不符合则返回false,符合则下一步
2、死循环,判断workerCount是否大于上限,或者大于corePoolSize/maximumPoolSize,没有的话则对workerCount+1操作,
3、如果不符合上述判断或+1操作失败,再次获取线程池的控制状态,获取runState与刚开始获取的runState相比,不一致则跳出内层循环继续外层循环,否则继续内层循环
4、+1操作成功后,使用重入锁ReentrantLock来保证往workers当中添加worker实例,添加成功就启动该实例。

接下来看看流程图来理解一下上面代码的一个执行流程

3.addWorkerFailed(Worker w)

addWorker方法添加worker失败,并且没有成功启动任务的时候,就会调用此方法,将任务从workers中移除,并且workerCount做-1操作。

      private void addWorkerFailed(Worker w) {
          //重入锁
          final ReentrantLock mainLock = this.mainLock;
          //获取锁
          mainLock.lock();
          try {
              //如果worker不为null
              if (w != null)
                  //workers移除worker
                  workers.remove(w);
              //通过CAS操作,workerCount-1
              decrementWorkerCount();
              tryTerminate();
          } finally {
              //释放锁
              mainLock.unlock();
          }
      }

4.tryTerminate()

当对线程池执行了非正常成功逻辑的操作时,都会需要执行tryTerminate尝试终止线程池

      final void tryTerminate() {
          //死循环
          for (;;) {
              //获取线程池控制状态
              int c = ctl.get();

              /*
              *线程池处于RUNNING状态
              *线程池状态最小大于TIDYING
              *线程池==SHUTDOWN并且workQUeue不为空
              *直接return,不能终止
              */
              if (isRunning(c) ||
                  runStateAtLeast(c, TIDYING) ||
                  (runStateOf(c) == SHUTDOWN && ! workQueue.isEmpty()))
                  return;

              //如果workerCount不为0
              if (workerCountOf(c) != 0) { // Eligible to terminate
                  interruptIdleWorkers(ONLY_ONE);
                  return;
              }
      ​
              //获取线程池的锁
              final ReentrantLock mainLock = this.mainLock;
              //获取锁
              mainLock.lock();
              try {
                  //通过CAS操作,设置线程池状态为TIDYING
                  if (ctl.compareAndSet(c, ctlOf(TIDYING, 0))) {
                      try {
                          terminated();
                      } finally {
                          //设置线程池的状态为TERMINATED
                          ctl.set(ctlOf(TERMINATED, 0));
                          //发送释放信号给在termination条件上等待的线程
                          termination.signalAll();
                      }
                      return;
                  }
              } finally {
                  //释放锁
                  mainLock.unlock();
              }
              // else retry on failed CAS
          }
      }

5.runWorker(Worker w)

该方法的作用就是去执行任务

final void runWorker(Worker w) {
      //获取当前线程
      Thread wt = Thread.currentThread();
      //获取worker里的任务
      Runnable task = w.firstTask;
      //将worker实例的任务赋值为null
      w.firstTask = null;

      /*
      *unlock方法会调用AQS的release方法
      *release方法会调用具体实现类也就是Worker的tryRelease方法
      *也就是将AQS状态置为0,允许中断
      */
      w.unlock(); // allow interrupts
      //是否突然完成
      boolean completedAbruptly = true;
      try {
          //worker实例的task不为空,或者通过getTask获取的不为空
          while (task != null || (task = getTask()) != null) {
              //获取锁
              w.lock();
              // If pool is stopping, ensure thread is interrupted;
              // if not, ensure thread is not interrupted.  This
              // requires a recheck in second case to deal with
              // shutdownNow race while clearing interrupt
              /*
              *获取线程池的控制状态,至少要大于STOP状态
              *如果状态不对,检查当前线程是否中断并清除中断状态,并且再次检查线程池状态是否大于STOP
              *如果上述满足,检查该对象是否处于中断状态,不清除中断标记
              */
              if ((runStateAtLeast(ctl.get(), STOP) ||
                   (Thread.interrupted() &&
                    runStateAtLeast(ctl.get(), STOP))) &&
                  !wt.isInterrupted())
                  //中断改对象
                  wt.interrupt();
              try {
                  //执行前的方法,由子类具体实现
                  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(task, thrown);
                  }
              } finally {//执行完后
                  //task设置为null
                  task = null;
                  //已完成任务数+1
                  w.completedTasks++;
                  //释放锁
                  w.unlock();
              }
          }
          completedAbruptly = false;
      } finally {
          //处理并退出当前worker
          processWorkerExit(w, completedAbruptly);
      }
  }

接下来我们用文字来说明一下执行任务这个方法的具体逻辑和流程。

  1. 首先在方法一进来,就执行了w.unlock(),这是为了将AQS的状态改为0,因为只有getState() >= 0的时候,线程才可以被中断;
  2. 判断firstTask是否为空,为空则通过getTask()获取任务,不为空接着往下执行
  3. 判断是否符合中断状态,符合的话设置中断标记
  4. 执行beforeExecute(),task.run(),afterExecute()方法
  5. 任何一个出异常都会导致任务执行的终止;进入processWorkerExit来退出任务
  6. 正常执行的话会接着回到步骤2

附上一副简单的流程图:

6.getTask()

在上面的runWorker方法当中我们可以看出,当firstTask为空的时候,会通过该方法来接着获取任务去执行,那我们就看看获取任务这个方法到底是怎么样的?

      private Runnable getTask() {
          //标志是否获取任务超时
          boolean timedOut = false; // Did the last poll() time out?
      ​
          //死循环
          for (;;) {
              //获取线程池的控制状态
              int c = ctl.get();
              //获取线程池的runState
              int rs = runStateOf(c);
      ​
              // Check if queue empty only if necessary.
              /*
              *判断线程池的状态,出现以下两种情况
              *1、runState大于等于SHUTDOWN状态
              *2、runState大于等于STOP或者阻塞队列为空
              *将会通过CAS操作,进行workerCount-1并返回null
              */
              if (rs >= SHUTDOWN && (rs >= STOP || workQueue.isEmpty())) {
                  decrementWorkerCount();
                  return null;
              }
      ​
              //获取线程池的workerCount
              int wc = workerCountOf(c);
      ​
              // Are workers subject to culling?

              /*
              *allowCoreThreadTimeOut:是否允许core Thread超时,默认false
              *workerCount是否大于核心核心线程池
              */
              boolean timed = allowCoreThreadTimeOut || wc > corePoolSize;
      ​
              /*
              *1、wc大于maximumPoolSize或者已超时
              *2、队列不为空时保证至少有一个任务
              */
              if ((wc > maximumPoolSize || (timed && timedOut))
                  && (wc > 1 || workQueue.isEmpty())) {
                  /*
                  *通过CAS操作,workerCount-1
                  *能进行-1操作,证明wc大于maximumPoolSize或者已经超时
                  */
                  if (compareAndDecrementWorkerCount(c))
                      //-1操作成功,返回null
                      return null;
                  //-1操作失败,继续循环
                  continue;
              }
      ​
              try {
                  /*
                  *wc大于核心线程池
                  *执行poll方法
                  *小于核心线程池
                  *执行take方法
                  */
                  Runnable r = timed ?
                      workQueue.poll(keepAliveTime, TimeUnit.NANOSECONDS) :
                  workQueue.take();
                  //判断任务不为空返回任务
                  if (r != null)
                      return r;
                  //获取一段时间没有获取到,获取超时
                  timedOut = true;
              } catch (InterruptedException retry) {
                  timedOut = false;
              }
          }
      }

还是文字解说一下上面的代码逻辑和流程:

  1. 获取线程池控制状态和runState,判断线程池是否已经关闭或者正在关闭,是的话则workerCount-1操作返回null
  2. 获取workerCount判断是否大于核心线程池
  3. 判断workerCount是否大于最大线程池数目或者已经超时,是的话workerCount-1,-1成功则返回null,不成功则回到步骤1重新继续
  4. 判断workerCount是否大于核心线程池,大于则用poll方法从队列获取任务,否则用take方法从队列获取任务
  5. 判断任务是否为空,不为空则返回获取的任务,否则回到步骤1重新继续

接下来依然有一副流程图:

7.processWorkerExit

明显的,在执行任务当中,会去获取任务进行执行,那既然是执行任务,肯定就会有执行完或者出现异常中断执行的时候,那这时候肯定也会有相对应的操作,至于具体操作是怎么样的,我们还是直接去看源码最实际。

     private void processWorkerExit(Worker w, boolean completedAbruptly) {
          /*
          *completedAbruptly:在runWorker出现,代表是否突然完成的意思
          *也就是在执行任务过程当中出现异常,就会突然完成,传true
          *
          *如果是突然完成,需要通过CAS操作,workerCount-1
          *不是突然完成,则不需要-1,因为getTask方法当中已经-1
          *
          *下面的代码注释貌似与代码意思相反了
          */
          if (completedAbruptly) // If abrupt, then workerCount wasn't adjusted
              decrementWorkerCount();
      ​
          //生成重入锁
          final ReentrantLock mainLock = this.mainLock;
          //获取锁
          mainLock.lock();
          try {
              //线程池统计的完成任务数completedTaskCount加上worker当中完成的任务数
              completedTaskCount += w.completedTasks;
              //从HashSet<Worker>中移除
              workers.remove(w);
          } finally {
              //释放锁
              mainLock.unlock();
          }
      ​
          //因为上述操作是释放任务或线程,所以会判断线程池状态,尝试终止线程池
          tryTerminate();
      ​
          //获取线程池的控制状态
          int c = ctl.get();

          //判断runState是否小鱼STOP,即是RUNNING或者SHUTDOWN
          //如果是RUNNING或者SHUTDOWN,代表没有成功终止线程池
          if (runStateLessThan(c, STOP)) {
              /*
              *是否突然完成
              *如若不是,代表已经没有任务可获取完成,因为getTask当中是while循环
              */
              if (!completedAbruptly) {
                  /*
                  *allowCoreThreadTimeOut:是否允许core thread超时,默认false
                  *min-默认是corePoolSize
                  */
                  int min = allowCoreThreadTimeOut ? 0 : corePoolSize;
                  //允许core thread超时并且队列不为空
                  //min为0,即允许core thread超时,这样就不需要维护核心核心线程池了
                  //如果workQueue不为空,则至少保持一个线程存活
                  if (min == 0 && ! workQueue.isEmpty())
                      min = 1;
                  //如果workerCount大于min,则表示满足所需,可以直接返回
                  if (workerCountOf(c) >= min)
                      return; // replacement not needed
              }
              //如果是突然完成,添加一个空任务的worker线程--这里我也不太理解
              addWorker(null, false);
          }
      }
  1. 首先判断线程是否突然终止,如果是突然终止,通过CAS,workerCount-1
  2. 统计线程池完成任务数,并将worker从workers当中移除
  3. 判断线程池状态,尝试终止线程池
  4. 线程池没有成功终止
    • 判断是否突然完成任务,不是则进行下一步,是则进行第三步
    • 如允许核心线程超时,队列不为空,则至少保证一个线程存活
    • 添加一个空任务的worker线程

Worker内部类

​ 我们在上面已经算是挺详细地讲了线程池执行任务execute的执行流程和一些细节,在上面频繁地出现了一个字眼,那就是worker实例,那么这个worker究竟是什么呢?里面都包含了一些什么信息,以及worker这个任务究竟是怎么执行的呢?

​ 我们就在这个部分来介绍一下吧,还是直接上源码:

我们可以看到Worker内部类继承AQS同步器并且实现了Runnable接口,所以Worker很明显就是一个可执行任务并且又可以控制中断、起到锁效果的类。

  private final class Worker
          extends AbstractQueuedSynchronizer
          implements Runnable
      {
          /**
           * This class will never be serialized, but we provide a
           * serialVersionUID to suppress a javac warning.
           */
          private static final long serialVersionUID = 6138294804551838833L;
  ​
          /** 工作线程,如果工厂失败则为空. */
          final Thread thread;
          /** 初始化任务,有可能为空 */
          Runnable firstTask;
          /** 已完成的任务计数 */
          volatile long completedTasks;
  ​
          /**
           * 创建并初始化第一个任务,使用线程工厂来创建线程
           * 初始化有3步
           *1、设置AQS的同步状态为-1,表示该对象需要被唤醒
           *2、初始化第一个任务
           *3、调用ThreadFactory来使自身创建一个线程,并赋值给worker的成员变量thread
           */
          Worker(Runnable firstTask) {
              setState(-1); // inhibit interrupts until runWorker
              this.firstTask = firstTask;
              this.thread = getThreadFactory().newThread(this);
          }
  ​
    //重写Runnable的run方法
          /** Delegates main run loop to outer runWorker  */
          public void run() {
              //调用ThreadPoolExecutor的runWorker方法
              runWorker(this);
          }
  ​
          // Lock methods
          //
          // The value 0 represents the unlocked state.
          // The value 1 represents the locked state.
    //代表是否独占锁,0-非独占  1-独占
          protected boolean isHeldExclusively() {
              return getState() != 0;
          }

    //重写AQS的tryAcquire方法尝试获取锁
          protected boolean tryAcquire(int unused) {
           //尝试将AQS的同步状态从0改为1
              if (compareAndSetState(0, 1)) {
               //如果改变成,则将当前独占模式的线程设置为当前线程并返回true
                  setExclusiveOwnerThread(Thread.currentThread());
                  return true;
              }
              //否则返回false
              return false;
          }
  ​
    //重写AQS的tryRelease尝试释放锁
          protected boolean tryRelease(int unused) {
           //设置当前独占模式的线程为null
              setExclusiveOwnerThread(null);
              //设置AQS同步状态为0
              setState(0);
              //返回true
              return true;
          }
  ​
    //获取锁
          public void lock()        { acquire(1); }
          //尝试获取锁
          public boolean tryLock()  { return tryAcquire(1); }
          //释放锁
          public void unlock()      { release(1); }
          //是否被独占
          public boolean isLocked() { return isHeldExclusively(); }
  ​
          void interruptIfStarted() {
              Thread t;
              if (getState() >= 0 && (t = thread) != null && !t.isInterrupted()) {
                  try {
                      t.interrupt();
                  } catch (SecurityException ignore) {
                  }
              }
          }
  }

小结

写这个线程池就真的是不容易了,历时两个星期,中途有很多的地方不懂,而且《Java并发编程的艺术》的这本书当中对线程池的介绍其实并不算多,所以自己看起来也挺痛苦的,还经常会看了这个方法就不知道为什么要调用这个以及调用这个方法是出何用意。而且在这学习的过程当中,有在怀疑自己的学习方法对不对,因为也有人跟我说不需要一句句去看去分析源码,只需要知道流程就可以了,但是后来还是想想按照自己的学习路线走,多读源码总是有好处的,在这里我也给程序猿一些建议,有自己的学习方法的时候,按照自己的方式坚定走下去。

参考资料

方腾飞:《Java并发编程的艺术》

如需转载,请务必注明出处,毕竟一块块搬砖也不是容易的事情。

版权声明:本文版权归作者所有,遵循 CC 4.0 BY-SA 许可协议, 转载请注明原文链接
https://www.cnblogs.com/KingJack/p/9595621.html

【java并发入门】01 并发编程bug的源头_大数据王小皮-编程思维

一、根本原因 「CPU、内存、磁盘之间的速度差异」 为了能同时执行多个任务,CPU 发展出时间片轮转、多核等 CPU 要从内存中读数据太慢了,所以给自己设置了缓存 CPU 读磁盘更慢了,所以可以让该线程阻塞 二、直接原因 缓存导致的可见性问题 CPU 把要处理的数据加载到自己的缓存中,处理完了放回自己的缓存。 另一

【java并发入门】02 java内存模型:看java如何解决可见性和有序性问题_大数据王小皮-编程思维

如何解决其中的可见性和有序性导致的问题,这也就引出来了今天的主角——Java 内存模型。 一、什么是 Java 内存模型? 导致可见性的原因是缓存,导致有序性的原因是编译优化,那解决可见性、有序性最直接的办法就是禁用缓存和编译优化,但这样虽然解决了问题,但也导致带来的性能优化都没了。 因此,解决方案是:提出一套规则和方

单向循环链表-约瑟夫问题_wiselee/-编程思维

单向环形列表 应用场景:约瑟夫环问题 思路: 创建第一个节点,让first指向该节点,并形成环状 后面当我们每创建一个新的节点,就把该节点,加入到已有的环形链表中即可 遍历环形链表 先让一个辅助变量,指向frist节点 然后通过一个while循环遍历该环形链表即可 curBoy.next == first结束

java并发之aqs同步器学习_kingjack-编程思维

AQS队列同步器学习 在学习并发的时候,我们一定会接触到 JUC 当中的工具,JUC 当中为我们准备了很多在并发中需要用到的东西,但是它们都是基于AQS(AbstractQueuedSynchronizer)队列同步器来实现的,也就是我们如果能够去梳理清楚AQS当中的知识点,对我们以后了解其他并发功能键有很大的帮助。

总算给女盆友讲明白了,如何使用stream流的filter()操作_良工说技术-编程思维

一、引言 在上一篇文章中《这么简单,还不会使用java8 stream流的map()方法吗?》分享了使用stream的map()方法,不知道小伙伴还有印象吗,先来回顾下要点,map()方法是把一个流中的元素T转换为另外一个新流中的元素R,转换完成后两个流的元素个数不发生改变,具体怎么使用,请小伙伴移步上篇查看。在上篇文

【java并发入门】01 并发编程bug的源头_大数据王小皮-编程思维

一、根本原因 「CPU、内存、磁盘之间的速度差异」 为了能同时执行多个任务,CPU 发展出时间片轮转、多核等 CPU 要从内存中读数据太慢了,所以给自己设置了缓存 CPU 读磁盘更慢了,所以可以让该线程阻塞 二、直接原因 缓存导致的可见性问题 CPU 把要处理的数据加载到自己的缓存中,处理完了放回自己的缓存。 另一

【java并发入门】02 java内存模型:看java如何解决可见性和有序性问题_大数据王小皮-编程思维

如何解决其中的可见性和有序性导致的问题,这也就引出来了今天的主角——Java 内存模型。 一、什么是 Java 内存模型? 导致可见性的原因是缓存,导致有序性的原因是编译优化,那解决可见性、有序性最直接的办法就是禁用缓存和编译优化,但这样虽然解决了问题,但也导致带来的性能优化都没了。 因此,解决方案是:提出一套规则和方

大厂敲门砖,github霸榜的顶级并发编程宝典被我搞到手了! - 编程思维

并发编程的目的是为了提高程序的执行速度,但是并不意味着启动更多的线程会达到更好的并发效果,并发编程还会引起死锁 , 上下文频繁切换 , 线程不安全等问题。并发编程作为Java程序员最重要的技能之一,也是最难掌握的一种技能。它要求编程者对计算机最底层的运作原理有深刻的理解,同时要求编程者逻辑清晰、思维缜密,这样才能写出高

java并发之aqs同步器学习_kingjack-编程思维

AQS队列同步器学习 在学习并发的时候,我们一定会接触到 JUC 当中的工具,JUC 当中为我们准备了很多在并发中需要用到的东西,但是它们都是基于AQS(AbstractQueuedSynchronizer)队列同步器来实现的,也就是我们如果能够去梳理清楚AQS当中的知识点,对我们以后了解其他并发功能键有很大的帮助。

【java并发入门】03 互斥锁(上):解决原子性问题_大数据王小皮-编程思维

原子性问题的源头是线程切换 Q:如果禁用 CPU 线程切换是不是就解决这个问题了? A:单核 CPU 可行,但到了多核 CPU 的时候,有可能是不同的核在处理同一个变量,即便不切换线程,也有问题。 所以,解决原子性的关键是「同一时刻只有一个线程处理该变量,也被称为互斥」。 如何做到呢?用「锁」。 一、锁模型 一)简易锁

持续发烧,聊聊dart语言的并发处理,能挑战go不?_汤哥搞开发-编程思维

前言 貌似关于Dart的文章没流量啊,就算在小编关怀上了首页,看得人还是很少的。 算了,今天持续发烧,再来写写如何使用 Dart 语言的并发操作。说起并发操作,玩 Go 的同学该笑了,这就是我们的看家本领啊。玩 PHP 的同学继续看看,表示我们光看不说话。 代码演示之前,我们先假设一个场景。假设我有一些漂亮妹妹,他们要