Java多线程-线程池学习总结


    1.使用线程池的原因

    线程过 多会使统性能降低,因为它会导致额外的上下文环境切换开销,甚至导致栈溢出OutOfMemoryError。
    • 减少线程创建和销毁的开销,每个工作线程都可重复的使用,执行多个任务;
    • 根据系统的能力设置线程的数量,访问线程数量过大造成系统内存的使用率过高;
    • 系统响应能力,有效的避免了很多情况下线程的创建所消耗的时间。

    2.线程池的分类

    Executors 此类是个工厂类/工具类,提供Executor、ExecutorService、ScheduledExecutorService、ThreadFactory 和 Callable 类的一些实用方法。
    ExecutorService 真正的线程池接口。
    ThreadPoolExecutor ThreadPoolExecutor是ExecutorService的一个实现类,它使用可能的几个池线程之一执行每个提交的任务,通常使用 Executors 工厂方法配置。
    类型
    说明
    public static ExecutorService newSingleThreadExecutor() {
        return new FinalizableDelegatedExecutorService
            (new ThreadPoolExecutor(1, 1,
                                    0L, TimeUnit.MILLISECONDS,
                                    new LinkedBlockingQueue<Runnable>()));
    }
    单线程的线程池。这个线程池只有一个线程在工作,也就是相当于单线程串行执行所有任务。如果这个唯一的线程因为异常结束,那么会有一个新的线程来替代它。此线程池保证所有任务的执行顺序按照任务的提交顺序执行。
    public static ExecutorService newFixedThreadPool(int nThreads) {
        return new ThreadPoolExecutor(nThreads, nThreads,
                                      0L, TimeUnit.MILLISECONDS,
                                      new LinkedBlockingQueue<Runnable>());
    }

    固定大小的线程池。

    corePoolSize和maximumPoolSize设置为参数中指定的值,并且创建的线程池不会超时。

    每次提交一个任务就创建一个线程,直到线程达到线程池的最大大小。线程池的大小一旦达到最大值就会保持不变,如果某个线程因为执行异常而结束,那么线程池会补充一个新线程。

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

    可缓存的线程池。

    线程池的corePoolSize设置为0,maximumPoolSize设置为Integer.MAX_VALUE,并将超时设置为1分钟,这种方式创建的线程池可以被无限扩展,并且当需求降低时会自动收缩。

    如果线程池的大小超过了处理任务所需要的线程,那么就会回收部分空闲(60秒不执行任务)的线程,当任务数增加时,此线程池又可以智能的添加新线程来处理任务。此线程池不会对线程池大小做限制,线程池大小完全依赖于操作系统(或者说JVM)能够创建的最大线程大小。

    public static ScheduledExecutorService newScheduledThreadPool(int corePoolSize) {
        return new ScheduledThreadPoolExecutor(corePoolSize);
    }
    固定大小的线程池。此线程池支持定时以及周期性执行任务的需求。

    3.ThreadPoolExecutor构造函数详解

    ThreadPoolExecutor是Executors类的底层实现,ThreadPoolExecutor的完整构造函数:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    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;
    }
    参数
    说明
    int corePoolSize
    核心池的大小:在创建了线程池后,默认情况下,线程池中并没有任何线程,而是等待有任务到来才创建线程去执行任务,除非调用了prestartAllCoreThreads()或者prestartCoreThread()方法,从这2个方法的名字就可以看出,是预创建线程的意思,即在没有任务到来之前就创建corePoolSize个线程或者一个线程。默认情况下,在创建了线程池后,线程池中的线程数为0,当有任务来之后,就会创建一个线程去执行任务,当线程池中的线程数目达到corePoolSize后,就会把到达的任务放到阻塞队列当中
    int maximumPoolSize
    线程池最大线程数:线程池允许创建的最大线程数。如果阻塞队列满了,并且已创建的线程数小于最大线程数,则线程池会再创建新的线程执行任务。值得注意的是如果使用了无界的任务队列这个参数就没什么效果。
    long keepAliveTime

    线程活动保持时间:表示线程没有任务执行时最多保持多久时间会终止。默认情况下,只有当线程池中的线程数大于corePoolSize时,keepAliveTime才会起作用,即当线程池中的线程数大于corePoolSize时,如果一个线程空闲的时间达到keepAliveTime,则会终止,直到线程池中的线程数不超过corePoolSize。但是如果调用了allowCoreThreadTimeOut(boolean)方法,在线程池中的线程数不大于corePoolSize时,keepAliveTime参数也会起作用,直到线程池中的线程数为0。

    keepAliveTime和maximumPoolSize及BlockingQueue的类型均有关系。

    如果BlockingQueue是无界的,那么永远不会触发maximumPoolSize,自然keepAliveTime也就没有意义。

    反之,如果有界BlockingQueue数值又较小,线程核心数较小,同时keepAliveTime又设的很小,如果任务频繁,那么系统就会频繁的申请回收线程。

    TimeUnit unit

    线程活动保持时间的单位:

    TimeUnit.DAYS;                         //天
    TimeUnit.HOURS;                     //小时
    TimeUnit.MINUTES;                  //分钟
    TimeUnit.SECONDS;                //秒
    TimeUnit.MILLISECONDS;       //毫秒
    TimeUnit.MICROSECONDS;    //微妙
    TimeUnit.NANOSECONDS;     //纳秒

    BlockingQueue<Runnable> workQueue

    阻塞队列:用于保存等待执行的任务的阻塞队列

    • ArrayBlockingQueue:是一个基于数组结构的有界阻塞队列,此队列按 FIFO(先进先出)原则对元素进行排序。
    • LinkedBlockingQueue:一个基于链表结构的无界阻塞队列,此队列按 FIFO(先进先出)排序元素,吞吐量通常要高于ArrayBlockingQueue。静态工厂方法Executors.newFixedThreadPool()使用了这个队列。
    • SynchronousQueue(同步移交):对于非常大的或者无界的线程池来说,可以通过SynchronousQueue来避免任务排队,以及直接将任务从生产者移交给工作者线程。
      SynchronousQueue不是一个真正的队列,而是一种在线程之间进行移交的机制。要将一个元素放入SynchronousQueue中,必须有另一个线程正在等待接受这个元素。如果没有线程正在等待,并且线程池的当前大小小于最大值,那么ThreadPoolExecutor将创建一个新的线程;否则根据饱和策略,这个任务将被拒绝。使用直接移交将更高效,因为任务会直接移交给执行它的线程,而不是放在队列中,然后由工作者线程从队列中提取该任务。SynchronousQueue吞吐量通常要高于LinkedBlockingQueue,静态工厂方法Executors.newCachedThreadPool()使用了这个队列。
      只有当线程池是无界的或者可以拒绝任务时,SynchronousQueue才有实际价值。
    • PriorityBlockingQueue:一个具有优先级的无界阻塞队列。
    ThreadFactory threadFactory
    线程工厂:每当线程池需要创建一个线程时,都是通过线程工厂方法来完成的。默认的线程工厂将创建一个新的、非守护的线程,并且包含特殊的配置信息。通过指定一个线程工厂方法,可以定制线程池的配置信息。在ThreadFactory中只定义一个方法newThread,每当线程池需要创建一个新线程时都会调用这个方法。
    RejectedExecutionHandler handler

    拒绝策略:当线程池和阻塞队列都满了,或者线程池调用了shutdown()、shutdownNow(),说明线程池处于饱和状态,那么必须采取一种策略处理提交的新任务。默认拒绝策略是AbortPolicy,表示无法处理新任务时抛出异常RejectedExecutionException。

    • AbortPolicy:当任务添加到线程池中被拒绝时,它将抛出 RejectedExecutionException 异常。默认拒绝策略。
    • CallerRunsPolicy:实现了一种调节机制,该策略既不会抛弃任务,也不会抛出异常,而是将某些任务回退到调用者。这个策略显然不想放弃执行任务,但是由于线程池中已饱和,那么就直接使用调用者所在线程来运行任务,能够减缓新任务的提交速度,降低新任务的流量。
    • DiscardPolicy:当任务添加到线程池中被拒绝时,线程池将丢弃被拒绝的任务,不执行。
    • DiscardOldestPolicy:当任务添加到线程池中被拒绝时,线程池会抛弃阻塞队列中下一个(即最旧的)将被执行的任务,然后将被拒绝的任务添加到阻塞队列中。(如果工作队列是一个优先队列,那么 DisacrdOldestPolicy 将导致抛弃优先级最高的任务,因此最好不要将DiscardOldest与优先级队列放在一起使用)。

    4.线程池的状态及流转

    线程池的状态:

    • private static final int RUNNING = -<< COUNT_BITS;            接收新的任务并且处理队列中的任务。
    • private static final int SHUTDOWN << COUNT_BITS;         不接收新的任务,但是处理队列中的任务。
    • private static final int STOP << COUNT_BITS;                    不接收新的任务,也不处理队列中的任务,并且中断正在运行中的任务。调用方法shutdownNow();
    • private static final int TIDYING << COUNT_BITS;               所有的任务都已经终止,工作的线程数量为0,所有的线程过渡到TIDYING状态,将会调用terminated()钩子方法。
    • private static final int TERMINATED << COUNT_BITS;       terminated()方法已经完成。

    状态过渡流程:

    • RUNNING -> SHUTDOWN:                        调用shutdown()
    • (RUNNING or SHUTDOWN) -> STOP:       调用shutdownNow()
    • SHUTDOWN -> TIDYING:                          当队列和线程池都为空时
    • STOP -> TIDYING:                                     当线程池为空
    • TIDYING -> TERMINATED:                        所有工作线程已经销毁,任务缓存队列已经清空或执行结束后,当terminated()方法执行完成线程池状态变为TERMINATED

    5.线程池处理流程

    当提交一个新任务到线程池时,线程池的处理流程如下:

    1. 首先线程池判断核心线程池是否已满?没满,创建一个工作线程来执行任务。满了,则进入下个流程。corePoolSize 为 0 时是一种特殊情况,此时即使工作队列没有饱和,向线程池第一次提交任务时仍然会创建新的线程。
    2. 其次线程池判断是否有空闲线程?若是,则使用空闲线程来执行任务。否则进入下个流程。
    3. 接着线程池判断工作队列是否已满?没满,则将新提交的任务存储在工作队列里。满了,则进入下个流程。
    4. 最后线程池判断整个线程池是否已满?没满,则创建一个新的工作线程来执行任务。满了,则交给饱和策略来处理这个任务。
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    // ctl是一个AtomInteger,低29位表示线程数workerCount,高3位表示线程池运行状态runState
    private  final  AtomicInteger ctl =  new  AtomicInteger(ctlOf(RUNNING,  0 ));
    private  static  final  int  COUNT_BITS = Integer.SIZE -  3 // Integer.SIZE = 32
    private  static  final  int  CAPACITY   = ( 1  << COUNT_BITS) -  1 ; // 00011111111111111111111111111111
     
    // runState is stored in the high-order bits
    private  static  final  int  RUNNING    = - 1  << COUNT_BITS;  // 11100000000000000000000000000000
    private  static  final  int  SHUTDOWN   =   0  << COUNT_BITS;  // 00000000000000000000000000000000
    private  static  final  int  STOP       =   1  << COUNT_BITS;  // 00100000000000000000000000000000
    private  static  final  int  TIDYING    =   2  << COUNT_BITS;  // 01000000000000000000000000000000
    private  static  final  int  TERMINATED =   3  << COUNT_BITS;  // 01100000000000000000000000000000
     
    /**
      * 这个方法用于取出runState的值 因为CAPACITY值为:00011111111111111111111111111111
      * ~为按位取反操作,则~CAPACITY值为:11100000000000000000000000000000
      * 再同参数做&操作,就将低29位置0了,而高3位还是保持原先的值,也就是runState的值
      *
      * @param c,该参数为存储runState和workerCount的int值
      * @return runState的值
      */
    private  static  int  runStateOf( int  c) {
         return  c & ~CAPACITY;
    }
     
    /**
      * 这个方法用于取出workerCount的值,因为CAPACITY值为:00011111111111111111111111111111,
      * 所以&操作将参数的高3位置0了,保留参数的低29位,也就是workerCount的值
      *
      * @param c,存储runState和workerCount的int值
      * @return workerCount的值
      */
    private  static  int  workerCountOf( int  c) {
         return  c & CAPACITY;
    }
     
    /**
      * 将runState和workerCount存到同一个int中
      *
      * @param rs,runState移位过后的值,负责填充返回值的高3位
      * @param wc,workerCount移位过后的值,负责填充返回值的低29位
      * @return 两者或运算过后的值
      */
    private  static  int  ctlOf( int  rs,  int  wc) {
         return  rs | wc;
    }
     
    // 只有RUNNING状态会小于0
    private  static  boolean  isRunning( int  c) {
         return  c < SHUTDOWN;
    }

    execute

    给线程池加入任务的方法为execute方法,该方法策略大概分三步:

           1)活动线程小于corePoolSize的时候创建新的线程;
           2)活动线程大于corePoolSize时都是先加入到任务队列当中;
           3)任务队列满了再去启动新的线程,如果线程数达到最大值就拒绝任务。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    public  void  execute(Runnable command) {
         if  (command ==  null )
             throw  new  NullPointerException();
         int  c = ctl.get();
         if  (workerCountOf(c) < corePoolSize) { // 当前线程数小于核心线程数corePoolSize,新建核心线程处理任务
             if  (addWorker(command,  true ))  // 直接启动新的线程。第二个参数true,addWorker中会重新检查workerCount是否小于corePoolSize
                 return ;
             c = ctl.get();
         }
         // 活动线程数 >= corePoolSize
         // runState为RUNNING && 队列未满
         if  (isRunning(c) && workQueue.offer(command)) {
             int  recheck = ctl.get(); // 双重检查
             
             if  (! isRunning(recheck) && remove(command)) // 非RUNNING状态,则从workQueue中移除任务并拒绝任务
                 reject(command); // 采用线程池指定的策略拒绝任务
             else  if  (workerCountOf(recheck) ==  0 ) // 线程池处于RUNNING状态 || 线程池处于非RUNNING状态但是任务移除失败
                 addWorker( null false ); // addWorker()这行代码是为了SHUTDOWN状态下没有活动线程了,但是队列里还有任务没执行这种特殊情况。
                 // 添加一个null任务是因为SHUTDOWN状态下,线程池不再接受新任务
         
         // 两种情况:
         // 1.非RUNNING状态拒绝新的任务
         // 2.队列满了启动新的线程失败(workCount > maximumPoolSize)
         }
         else  if  (!addWorker(command,  false ))
             reject(command);
    }

    addWorker

    addWorker即创建新线程。检查在当前线程池状态和限制下能否创建一个新线程,如果可以,会相应改变workerCount,每个worker都会运行他们的firstTask。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    // 第一个参数是需要运行的任务
    // 第二个参数是为了区分核心线程和非核心线程,用来确定线程池的边界是corePoolSize还是maxPoolSize
    // 本方法的作用就是新建一个worker线程并启动
    private  boolean  addWorker(Runnable firstTask,  boolean  core) {
         retry:
         for  (;;) {
             int  c = ctl.get();
             int  rs = runStateOf(c);
     
             // Check if queue empty only if necessary.
             // 这条语句等价:rs >= SHUTDOWN && (rs != SHUTDOWN || firstTask != null || workQueue.isEmpty())
             // 满足下列调价则直接返回false,线程创建失败:
             // rs > SHUTDOWN:STOP || TIDYING || TERMINATED 此时不再接受新的任务,且所有任务执行结束
             // rs = SHUTDOWN:firstTask != null 此时不再接受任务,但是仍然会执行队列中的任务
             // rs = SHUTDOWN:firstTask == null 见execute方法的addWorker(null, false),任务为null && 队列为空
             // 最后一种情况也就是说SHUTDOWN状态下,如果队列不为空还得接着往下执行,为什么?add一个null任务目的到底是什么?
             // 看execute方法只有workCount == 0的时候firstTask才会为null,结合这里的条件就是线程池SHUTDOWN了不再接受新任务,
             // 但是此时队列不为空,那么还得创建线程把任务给执行完才行。
             if  (rs >= SHUTDOWN && ! (rs == SHUTDOWN && firstTask ==  null  && ! workQueue.isEmpty()))
                 return  false ;
     
             // 走到这的情形:
             // 1.线程池状态为RUNNING
             // 2.SHUTDOWN状态,但队列中还有任务需要执行
             for  (;;) { // 循环确保CAS操作成功
                 int  wc = workerCountOf(c);
                 if  (wc >= CAPACITY || wc >= (core ? corePoolSize : maximumPoolSize))
                     return  false ;
                 
                 if  (compareAndIncrementWorkerCount(c)) // 成功增加workerCount则跳出外层循环,开始新建线程
                     break  retry;
                 c = ctl.get();   // 重读 ctl
                 if  (runStateOf(c) != rs) // 如果线程池的状态发生变化则重试循环
                     continue  retry;
                 // else CAS failed due to workerCount change; retry inner loop
             }
         }
     
         // wokerCount递增成功
         boolean  workerStarted =  false ;
         boolean  workerAdded =  false ;
         Worker w =  null ;
         try  {
             final  ReentrantLock mainLock =  this .mainLock;
             w =  new  Worker(firstTask); // 新建worker线程
             final  Thread t = w.thread;
             if  (t !=  null ) {
                 mainLock.lock(); // 并发的访问线程池workers对象必须加锁
                 try  {
                     // Recheck while holding lock.
                     // Back out on ThreadFactory failure or if
                     // shut down before lock acquired.
                     int  c = ctl.get();
                     int  rs = runStateOf(c);
     
                     if  (rs < SHUTDOWN || (rs == SHUTDOWN && firstTask ==  null )) {
                         if  (t.isAlive())  // 预检查 t 是否是startable
                             throw  new  IllegalThreadStateException();
                         
                         workers.add(w); // 将新建的线程放入线程池中,其实就是一个HashSet
                         int  s = workers.size();
                         if  (s > largestPoolSize) // 更新largestPoolSize
                             largestPoolSize = s;
                         workerAdded =  true ;
                     }
                 finally  {
                     mainLock.unlock();
                 }
                 // 启动新添加的线程,这个线程首先执行firstTask,然后不停的从队列中取任务执行
                 // 当等待keepAliveTime还没有任务执行则该线程结束。见runWorker和getTask方法的代码。
                 if  (workerAdded) {
                     t.start(); // 如果成功加入线程池则启动线程,最终执行的是ThreadPoolExecutor的runWoker方法
                     workerStarted =  true ;
                 }
             }
         finally  {
             if  (! workerStarted)
                 addWorkerFailed(w);
         }
         
         return  workerStarted; // 返回线程是否启动成功
    }

    内部类Worker是对任务的封装,所有submit的Runnable都被封装成了Worker,它本身也是一个Runnable, 然后利用AQS框架实现了一个简单的非重入的互斥锁, 实现互斥锁主要目的是为了中断的时候判断线程是在空闲还是运行,可以看后面shutdown和shutdownNow方法的分析。Worker中获取和释放锁相关代码如下:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    // Worker的构造方法
    Worker(Runnable firstTask) {
         setState(- 1 );  // 抑制线程中断setState(-1),inhibit interrupts until runWorker
         this .firstTask = firstTask;
         this .thread = getThreadFactory().newThread( this );
    }
      
    // state只有0和1,互斥
    // 之所以不用ReentrantLock是为了避免任务执行的代码中修改线程池的变量,如setCorePoolSize,因为ReentrantLock是可重入的。
    protected  boolean  tryAcquire( int  unused) {
         if  (compareAndSetState( 0 1 )) {
             setExclusiveOwnerThread(Thread.currentThread());
             return  true ; // 成功获得锁
         }
         return  false ; // 线程进入等待队列
    }
     
    protected  boolean  tryRelease( int  unused) {
         setExclusiveOwnerThread( null );
         setState( 0 );
         return  true ;
    }

    runWorker

    任务添加成功后实际执行的是runWorker这个方法,这个方法非常重要,简单来说它做的就是:
           1)第一次启动会执行初始化传进来的任务firstTask;
           2)然后会从workQueue中取任务执行,如果队列为空则等待keepAliveTime这么长时间。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    final  void  runWorker(Worker w) {
         Thread wt = Thread.currentThread();
         Runnable task = w.firstTask;
         w.firstTask =  null ;
         
         w.unlock(); // Worker的构造函数中抑制了线程中断setState(-1),所以这里需要unlock从而允许中断
     
         // 用于标识是否异常终止,finally中processWorkerExit的方法会有不同逻辑
         // 为true的情况:1.执行任务抛出异常;2.被中断。
         boolean  completedAbruptly =  true ;
         try  {
             // 如果getTask返回null那么getTask中会将workerCount递减,如果异常了这个递减操作会在processWorkerExit中处理
             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
                 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); // 和beforeExecute一样,留给子类去重载
                     }
                 finally  {
                     task =  null ;
                     w.completedTasks++;
                     w.unlock();
                 }
             }
     
             completedAbruptly =  false ;
         finally  {
             processWorkerExit(w, completedAbruptly); // 结束线程的一些清理工作
         }
    }

    getTask

    runWorker方法里有一段会调用getTask判断,主要用来取出队列中的任务。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    private  Runnable getTask() {
         boolean  timedOut =  false // Did the last poll() time out?
     
         retry:  for  (;;) {
             int  c = ctl.get();
             int  rs = runStateOf(c);
     
             // Check if queue empty only if necessary.
             // 1.rs > SHUTDOWN 所以rs至少等于STOP,这时不再处理队列中的任务
             // 2.rs = SHUTDOWN 所以rs>=STOP肯定不成立,这时还需要处理队列中的任务除非队列为空
             // 这两种情况都会返回null让runWoker退出while循环也就是当前线程结束了,所以必须要decrement wokerCount
             if  (rs >= SHUTDOWN && (rs >= STOP || workQueue.isEmpty())) {
                 decrementWorkerCount(); // 递减workerCount值
                 return  null ;
             }
     
             // 标记从队列中取任务时是否设置超时时间
             boolean  timed;  // Are workers subject to culling?
     
             // 1.RUNING状态
             // 2.SHUTDOWN状态,但队列中还有任务需要执行
             for  (;;) {
                 int  wc = workerCountOf(c);
     
                 // 1.core thread允许被超时,那么超过corePoolSize的线程必定有超时
                 // 2.allowCoreThreadTimeOut == false && wc > corePoolSize时,
                 // 一般都是这种情况,core thread即使空闲也不会被回收,只要超过的线程才会
                 timed = allowCoreThreadTimeOut || wc > corePoolSize;
     
                 // 从addWorker可以看到一般wc不会大于maximumPoolSize,所以更关心后面半句的情形:
                 // 1. timedOut == false 第一次执行循环, 从队列中取出任务不为null方法返回 或者 poll出异常了重试
                 // 2.timeOut == true && timed == false:看后面的代码workerQueue.poll超时时timeOut才为true,
                 // 并且timed要为false,这两个条件相悖不可能同时成立(既然有超时那么timed肯定为true)
                 // 所以超时不会继续执行而是return null结束线程。(重点:线程是如何超时的???)
                 if  (wc <= maximumPoolSize && !(timedOut && timed))
                     break ;
     
                 if  (compareAndDecrementWorkerCount(c)) // workerCount递减,结束当前thread
                     return  null ;
                 c = ctl.get();  // Re-read ctl
                 
                 if  (runStateOf(c) != rs) // 需要重新检查线程池状态,因为上述操作过程中线程池可能被SHUTDOWN
                     continue  retry;
                 // else CAS failed due to workerCount change; retry inner loop
             }
     
             try  {
                 // 1.以指定的超时时间从队列中取任务
                 // 2.core thread没有超时
                 Runnable r = timed ? workQueue.poll(keepAliveTime, TimeUnit.NANOSECONDS) : workQueue.take();
                 if  (r !=  null )
                     return  r;
                 timedOut =  true ; // 超时
             catch  (InterruptedException retry) {
                 timedOut =  false ; // 线程被中断重试
             }
         }
    }

    processWorkerExit

    线程退出会执行processWorkerExit()方法做一些清理工作。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    private  void  processWorkerExit(Worker w,  boolean  completedAbruptly) {
         // 正常的话再runWorker的getTask方法workerCount已经被减一了
         if  (completedAbruptly)
             decrementWorkerCount();
     
         final  ReentrantLock mainLock =  this .mainLock;
         mainLock.lock();
         try  {
             // 累加线程的completedTasks
             completedTaskCount += w.completedTasks;
             // 从线程池中移除超时或者出现异常的线程
             workers.remove(w);
         finally  {
             mainLock.unlock();
         }
     
         // 尝试停止线程池
         tryTerminate();
     
         int  c = ctl.get();
         // runState为RUNNING或SHUTDOWN
         if  (runStateLessThan(c, STOP)) {
             // 线程不是异常结束
             if  (!completedAbruptly) {
                 // 线程池最小空闲数,允许core thread超时就是0,否则就是corePoolSize
                 int  min = allowCoreThreadTimeOut ?  0  : corePoolSize;
                 // 如果min == 0但是队列不为空要保证有1个线程来执行队列中的任务
                 if  (min ==  0  && !workQueue.isEmpty())
                     min =  1 ;
                 // 线程池还不为空那就不用担心了
                 if  (workerCountOf(c) >= min)
                     return // replacement not needed
             }
             // 1.线程异常退出
             // 2.线程池为空,但是队列中还有任务没执行,看addWoker方法对这种情况的处理
             addWorker( null false );
         }
    }

    tryTerminate

    processWorkerExit方法中会尝试调用tryTerminate来终止线程池。这个方法在任何可能导致线程池终止的动作后执行:比如减少wokerCount或SHUTDOWN状态下从队列中移除任务。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    final  void  tryTerminate() {
         for  (;;) {
             int  c = ctl.get();
             // 以下状态直接返回:
             // 1.线程池还处于RUNNING状态
             // 2.SHUTDOWN状态但是任务队列非空
             // 3.runState >= TIDYING 线程池已经停止了或在停止了
             if  (isRunning(c) || runStateAtLeast(c, TIDYING) || (runStateOf(c) == SHUTDOWN && !workQueue.isEmpty()))
                 return ;
     
             // 只能是以下情形会继续下面的逻辑:结束线程池。
             // 1.SHUTDOWN状态,这时不再接受新任务而且任务队列也空了
             // 2.STOP状态,当调用了shutdownNow方法
     
             // workerCount不为0则还不能停止线程池,而且这时线程都处于空闲等待的状态
             // 需要中断让线程“醒”过来,醒过来的线程才能继续处理shutdown的信号。
             if  (workerCountOf(c) !=  0 ) {  // Eligible to terminate
                 // runWoker方法中w.unlock就是为了可以被中断,getTask方法也处理了中断。
                 // ONLY_ONE:这里只需要中断1个线程去处理shutdown信号就可以了。
                 interruptIdleWorkers(ONLY_ONE);
                 return ;
             }
     
             final  ReentrantLock mainLock =  this .mainLock;
             mainLock.lock();
             try  {
                 // 进入TIDYING状态
                 if  (ctl.compareAndSet(c, ctlOf(TIDYING,  0 ))) {
                     try  {
                         // 子类重载:一些资源清理工作
                         terminated();
                     finally  {
                         // TERMINATED状态
                         ctl.set(ctlOf(TERMINATED,  0 ));
                         // 继续awaitTermination
                         termination.signalAll();
                     }
                     return ;
                 }
             finally  {
                 mainLock.unlock();
             }
             // else retry on failed CAS
         }
    }

    shutdown和shutdownNow

    shutdown这个方法会将runState置为SHUTDOWN,会终止所有空闲的线程。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    public  void  shutdown() {
         final  ReentrantLock mainLock =  this .mainLock;
         mainLock.lock();
         try  {
             checkShutdownAccess();
             // 线程池状态设为SHUTDOWN,如果已经至少是这个状态那么则直接返回
             advanceRunState(SHUTDOWN);
             // 注意这里是中断所有空闲的线程:runWorker中等待的线程被中断 → 进入processWorkerExit →
             // tryTerminate方法中会保证队列中剩余的任务得到执行。
             interruptIdleWorkers();
             onShutdown();  // hook for ScheduledThreadPoolExecutor
         finally  {
             mainLock.unlock();
         }
         tryTerminate();
    }

    shutdownNow方法将runState置为STOP。和shutdown方法的区别,这个方法会终止所有的线程。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    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调用的是interruptIdleWorkers这个方法,而shutdownNow实际调用的是Worker类的interruptIfStarted方法:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    private  void  interruptIdleWorkers( boolean  onlyOne) {
         final  ReentrantLock mainLock =  this .mainLock;
         mainLock.lock();
         try  {
             for  (Worker w : workers) {
                 Thread t = w.thread;
                 // w.tryLock能获取到锁,说明该线程没有在运行,因为runWorker中执行任务会先lock,
                 // 因此保证了中断的肯定是空闲的线程。
                 if  (!t.isInterrupted() && w.tryLock()) {
                     try  {
                         t.interrupt();
                     catch  (SecurityException ignore) {
                     finally  {
                         w.unlock();
                     }
                 }
                 if  (onlyOne)
                     break ;
             }
         }
         finally  {
             mainLock.unlock();
         }
    }
     
    void  interruptIfStarted() {
         Thread t;
         // 初始化时state == -1
         if  (getState() >=  0  && (t = thread) !=  null  && !t.isInterrupted()) {
             try  {
                 t.interrupt();
             catch  (SecurityException ignore) {
             }
         }
    }

    6.线程池的关闭

    • 如果执行“//1”处“executor.shutdown();”,其会先抛出RejectedExecutionExeption异常,然后随机输出前7个线程的线程名称。
    • 如果执行“//2”中的“executor.shutdownNow();”,其会先抛出7个InterruptedException异常,然后抛出1个RejectedExecutionException异常。
    • 原因解析:
      • shutdown():调用了此方法,线程池的状态会从RUNNING状态过渡到SHUTDOWN状态,不会立即终止线程池,而是要等所有任务缓存队列中的任务都执行完后才终止,但再也不会接受新的任务,而是会抛出RejectedExecutionExeption异常,但是在异常被抛出之前,已经添加的任务会一直执行,直至完成。
      • shutdownNow():调用此方法,线程池的状态会从RUNNING状态过渡到STOP状态,并且会遍历调用线程池中每个线程的interrupt()方法,在每个线程中执行的任务被sleep()所阻塞,并且会响应中断。于是处于阻塞的7个线程抛出InterruptedException异常,再次添加新线程时,仍然像shutdown()一样,会被拒绝,并抛出RejectedExecutionException异常

    7.线程池的合理配置

    要想合理的配置线程池,就必须首先分析任务特性,可以从以下几个角度来进行分析:

    • 任务的性质:CPU密集型任务,IO密集型任务和混合型任务。
    • 任务的优先级:高,中和低。
    • 任务的执行时间:长,中和短。
    • 任务的依赖性:是否依赖其他系统资源,如数据库连接。

    任务性质不同的任务可以用不同规模的线程池分开处理。

    CPU密集型任务配置尽可能小的线程,如配置Ncpu+1个线程的线程池,可以使得每个线程都在执行任务。

    IO密集型任务则由于线程并不是一直在执行任务,则配置尽可能多的线程,如2*Ncpu。

    混合型的任务,如果可以拆分,则将其拆分成一个CPU密集型任务和一个IO密集型任务,只要这两个任务执行的时间相差不是太大,那么分解后执行的吞吐率要高于串行执行的吞吐率,如果这两个任务执行时间相差太大,则没必要进行分解。可以通过Runtime.getRuntime().availableProcessors()方法获得当前设备的CPU个数。

    当任务需要某种通过资源池来管理的资源时,例如数据库连接,那么线程池和资源池的大小将会互相影响。如果每个任务都需要一个数据库连接,那么连接池的大小就限制了线程池的大小。同样,当线程池中的任务是数据库连接的唯一使用者时,那么线程池的大小又将限制连接池的大小。

    8.线程池的监控

    通过线程池提供的参数进行监控:

    • taskCount:线程池需要执行的任务数量。
    • completedTaskCount:线程池在运行过程中已完成的任务数量。小于或等于taskCount。
    • largestPoolSize:线程池曾经创建过的最大线程数量。通过这个数据可以知道线程池是否满过。如等于线程池的最大大小,则表示线程池曾经满了。
    • getPoolSize:线程池的线程数量。如果线程池不销毁的话,线程池里的线程不会自动销毁,所以这个大小只增不减。
    • getActiveCount:获取活动的线程数。

    通过扩展线程池进行监控。通过继承线程池并重写线程池的beforeExecute,afterExecute和terminated方法,可以在任务执行前,执行后和线程池关闭前干一些事情。如监控任务的平均执行时间,最大执行时间和最小执行时间等。这几个方法在线程池里是空方法。如:protected void beforeExecute(Thread t, Runnable r) { }

    • 0
      点赞
    • 0
      收藏
      觉得还不错? 一键收藏
    • 0
      评论

    “相关推荐”对你有帮助么?

    • 非常没帮助
    • 没帮助
    • 一般
    • 有帮助
    • 非常有帮助
    提交
    评论
    添加红包

    请填写红包祝福语或标题

    红包个数最小为10个

    红包金额最低5元

    当前余额3.43前往充值 >
    需支付:10.00
    成就一亿技术人!
    领取后你会自动成为博主和红包主的粉丝 规则
    hope_wisdom
    发出的红包
    实付
    使用余额支付
    点击重新获取
    扫码支付
    钱包余额 0

    抵扣说明:

    1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
    2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

    余额充值