java开发之ThreadPoolExecutor源码分析

线程池的状态

只有了解线程池的几个状态,才能读懂它的核心源码。所以先说说这几个状态

running:为线程池初始化时的默认状态,此状态会接收任务进行处理

shutdown: 该状态下的线程池不接收任何任务,但会等待正在运行的任务执行完。通常调用shutdown() 方法完成设置

stop: 该状态的线程池不接收任何任务,同时java培训不会等待正在运行的任务执行完毕。通常调用shutdownNow() 方法完成设置

tidying:该状态下的线程池内,没有任何线程和任务

terminated:该状态为线程池的终态,通常调用tryTerminate()方法完成设置

大多数情况下线程池的一个生命周期流转大概是 running -> (shutdown,stop)-> tidying -> terminated

这几个状态在ThreadPoolExecutor源码中,通过一个ctl的整型原子变量标识,高3位标识线程状态,低29位标识线程数量。翻看源码就能看到

核心源码分析

  • execute(Runnable command)

为线程池的核心方法,调用该方法任务就会执行,直接看下面代码注释吧

( ) {
         (  )   ();
           .();

         (()  ) {
             ((, ))
                ;
              .();
        }

         (()  .()) {
               .();

             ( ()  ())

                ();

              (()  )
                (, );
        }

          ((, ))
            ();
    }

以上为核心源码的分析,无非就是根据线程池情况添加Worker、任务入队、执行拒绝策略。可以看看下面这个流程图,可能会更清晰

到这里,我们可以来讲讲addWorker 了。这个方法会封装成一个Worker对象,然后运行任务。看看Worker对象的类图:

Worker实现Runnable接口、继承AbstractQueuedSynchronizer,持有一个Thread的成员变量。所以可以把Worker对象看成一个线程,同时拥有AbstractQueuedSynchronizer的属性和方法,因此它能够进行加锁和释放锁的操作。

ok,逐步跟进来看看addWorker方法里面的逻辑。

( ,  ) {
        :
         (;;) {
               .();

               ();

             (
                 (   

                    .()))
                 ;

             (;;) {

                   ();

                 (
                      (   : ))
                     ;

                 (())
                     ;

                  .();
                 (()  )
                     ;
            }
        }

           ;
           ;
           ;
         {

               ();

                .;
             (  ) {
                    .;

                .();
                 {

                       (.());

                     (
                        (      )) {
                         (.())
                              ();

                        .();
                           .();
                         (  )
                              ;

                          ;
                    }
                }  {
                    .();
                }
                 () {

                    .();
                      ;
                }
            }
        }  {
             ( )
                ();
        }
         ;
    }

整体还不算复杂,核心就是根据传入的任务创建一个Worker对象,然后启动Worker。

下面来看看Worker启动的逻辑,前面说过了Worker实现Runnable接口,所以启动将会触发执行run方法,而run方法最终调的是runWorker()方法。

( ) {
           .();
           .;
        .  ;
        .();
           ;
         {

             (    (  ())  ) {

                .();

                 (((.(), )
                     (.()
                      (.(), )))
                    .())
                    .();
                 {

                    (, );
                       ;
                     {
                        .();
                    }  ( ) {
                          ;  ;
                    }  ( ) {
                          ;  ;
                    }  ( ) {
                          ;   ();
                    }  {

                        (, );
                    }
                }  {
                      ;

                    .;

                    .();
                }
            }
              ;
        }  {

            (, );
        }
    }

整个方法的逻辑其实也不算复杂,就是当前Worker不断死循环获取队列里面是否有任务。有,就加锁然后执行任务。无,就阻塞等待获取任务。那什么情况下才会跳出整个死循环,执行processWorkerExit呢?这里就需要看下getTask() 方法逻辑了。

() {
           ;
         (;;) {
               .();
               ();

             (    (    .())) {
                ();
                 ;
            }

               ();

                   ;

             ((    (  ))
                 (    .())) {
                 (())
                     ;
                ;
            }

             {

                    .(, .) :
                    .();
                 (  )
                     ;
                  ;
            }  ( ) {
                  ;
            }
        }
    }

最后,来看下processWorkerExit() 方法处理了哪些逻辑

( ,  ) {
         ()
            ();

            .;

        .();
         {

              .;
            .();
        }  {

            .();
        }

        ();

           .();
         ((, )) {
             () {
                      : ;
                 (     .())
                      ;
                 (()  )
                    ;
            }
            (, );
        }
    }

这个方法主要就是移除Worker对象,然后尝试将线程池的状态更改为terminate。这里需要讲一下tryTerminate方法逻辑,因为它和线程池awaitTermination()方法有一定的关联,来看看它的代码。

() {
         (;;) {
               .();

             (()
                (, )
                (()     .()))
                ;

             (()  ) {
                ();
                ;
            }

                .;

            .();
             {

                 (.(, (, ))) {
                     {

                        ();
                    }  {
                        .((, ));

                        .();
                    }
                    ;
                }
            }  {

                .();
            }

        }
    }

到这里,线程池execute方法大致的逻辑就完了。可以再看看时序图,理清下几个方法和类之间的调用。

  • shutdown()

中断线程池的线程,会等待正在执行的线程结束执行,来看看源码它是怎么实现的

() {
            .;

        .();
         {

            ();

            ();

            ();

            ();
        }  {

            .();
        }

        ();
    }

该方法我们比较关注的点是 interruptIdleWorkers方法,是怎样中断空闲Worker,然后是如何保证Worker执行完毕的?看看代码就知道了

( ) {
            .;

        .();
         {

             (  : ) {
                   .;

                 (.()  .()) {
                     {

                        .();
                    }  ( ) {
                    }  {
                        .();
                    }
                }
                 ()
                    ;
            }
        }  {
            .();
        }
    }

到这里,核心逻辑就是通过w这个锁来完成的。

  • shutdownNow

() {
         ;
            .;
        .();
         {
            ();
            ();
            ();
              ();
        }  {
            .();
        }
        ();
         ;      () {
            .;
        .();
         {
             (  : )
                .();
        }  {
            .();
        }
    }
    }

源码和shutdown差不多,只不过将线程池状态设置为stop,然后调用interruptWorkers 方法,看看worker方法。

() {
            .;
        .();
         {
             (  : )
                .();
        }  {
            .();
        }
    }

代码中并没有获取w锁的逻辑,所以这个方法会直接中断所有线程,并不会等待那些正在执行任务的worker把任务执行完。

  • awaitTermination

调用awaitTermination方法会一直阻塞等待线程池状态变为 terminated 才返回 或者等待超时返回。来看看代码就明白了

( ,  )
          {
           .();
            .;
        .();
         {
             (;;) {

                 ((.(), ))
                     ;
                 (  )
                     ;

                  .();
            }
        }  {
            .();
        }
    }

(1)处的代码已经告诉了该方法什么时候返回,就是mainLock锁的termination条件变量被唤醒返回。在上面分析中termination条件变量被唤醒是在执行tryTerminate()时完成的,因为内部调用termination.signalAll()。而tryTerminate() 方法被shutDown() 和shutDownNow() 调用过,所以如果要让awaitTermination 返回,调用这2个方法就行。


(0)

相关推荐

  • 并发编程之:深入解析线程池

    大家好,我是小黑,一个在互联网苟且偷生的农民工. 本期带来线程池的第二期内容,如果对线程池的基本概念还不是很清楚,可以先看我上一篇文章. 面试官:谈谈你对线程池的理解 本期内容会从以下几个方面解析线程 ...

  • 线程池全整理(附面试题)

    本文总结一下线程池是怎么回事,分以下几个部分,对哪个部分感兴趣,可以直接跳到对应的章节第一部分:线程池类的结构介绍第二部分:线程池的使用第三部分:线程池的创建流程第四部分:线程池的应用场景第五部分:线 ...

  • 写给小白看的线程池,还有10道面试题

    如何搞定20k的面试小抄 为什么要用线程池呢? 下面是一段创建线程并运行的代码: for (int i = 0; i < 100; i++) {     new Thread(() ->  ...

  • 万字长文爆肝线程池

    加个"星标",及时接收最新文章 这是程序员cxuan 的第 59 篇原创文章 更多文章见 https://github.com/crisxuan/bestJavaer 我们知道,线 ...

  • 10问10答:你真的了解线程池吗?

    <Java开发手册>中强调,线程资源必须通过线程池提供,而创建线程池必须使用ThreadPoolExecutor.手册主要强调利用线程池避免两个问题,一是线程过渡切换,二是避免请求过多时造 ...

  • 史上最清晰线程池实现原理剖析

    来源:albenw.github.io/posts/f5cda8d1/ 概要 线程池,大家都很熟悉了,我们在平时应用中也用的很多.对线程池,ThreadPoolExecutor 的实现原理有一定理解后 ...

  • 设计模式(一)——Java单例模式(代码+源码分析)

    设计模式(一)——Java单例模式(代码+源码分析)

  • 线程池ThreadPoolExecutor源码分析,看这一篇就够了

    前言 多线程是我们日常工作中很少能接触到的技术,但是面试的时候100%会被问到,万一工作中用到了基本不会,本篇咱们就来深入分析线程池的实现类ThreadPoolExecutor. 1.构造方法 构造方 ...

  • Java教程——LinkedHashMap源码分析

    大多企业级项目开发都会选择Java,这使得我们Java工程师们都是"全能型"人才,这使得项目经验成为了Java人面试的重头戏之一. 简介 LinkedHashMap内部维护了一个双 ...

  • Java Stream 源码分析

    前言 Java 8 的 Stream 使得代码更加简洁易懂,本篇文章深入分析 Java Stream 的工作原理,并探讨 Steam 的性能问题. Java 8 集合中的 Stream 相当于高级版的 ...

  • Java开发框架Spring源码分析

    我们平时使用springboot项目,可能就是继承spring-boot-starter-parent,然后引入一个spring-boot-stater-web依赖,然后创建一个继承于SpringBo ...

  • 设计模式(十五)——命令模式(Spring框架的JdbcTemplate源码分析)

    设计模式(十五)——命令模式(Spring框架的JdbcTemplate源码分析)

  • Qt update刷新之源码分析(一)

    在做GUI开发时,要让控件刷新,会调用update函数:那么在调用了update函数后,Qt究竟基于什么原理.执行了什么代码使得屏幕上有变化?本文就带大家来探究探究其内部源码. Qt手册中关于QWid ...

  • 【老孟Flutter】源码分析系列之InheritedWidget

    老孟导读:这是2021年源码系列的第一篇文章,其实源码系列的文章不是特别受欢迎,一个原因是原理性的知识非常枯燥,我自己看源码的时候特别有感触,二是想把源码分析讲的通俗易懂非常困难,自己明白 和 让别人 ...

  • Wifi模块—源码分析Wifi热点的开启(Android P)

    一 前言 Android使用一个修改版wpa_supplicant作为daemon来控制WIFI,它是一个安全中间件,代码位于external/wpa_supplicant,为各种无线网卡提供统一的安 ...