zoukankan      html  css  js  c++  java
  • 线程池实现原理及源码分析

    初步了解:

    多线程技术主要解决处理器单元内多个线程执行的问题,它可以显著减少处理器单元的闲置时间,增加处理器单元的吞吐能力。

    假设一个服务器完成一项任务所需时间为:T1 创建线程时间,T2 在线程中执行任务的时间,T3 销毁线程时间。
    如果:T1 + T3 远大于 T2,则可以采用线程池,以提高服务器性能。

    线程是稀缺资源,创建线程需要消耗较多的系统资源,还会降低系统的稳定性,合理的使用线程池对线程进行统一分配、调优和监控,有以下好处:
    1、降低资源消耗;
    2、提高响应速度;
    3、提高线程的可管理性。

     在什么情况下使用线程池? 

        1.单个任务处理的时间比较短 
        2.将需处理的任务的数量大 

    线程池适合应用的场合

    当一个Web服务器接受到大量短小线程的请求时,使用线程池技术是非常合适的,它可以大大减少线程的创建和销毁次数,提高服务器的工作效率。但如果线程要求的运行时间比较长,此时线程的运行时间比创建时间要长得多,单靠减少创建时间对系统效率的提高不明显,此时就不适合应用线程池技术,需要借助其它的技术来提高服务器的服务效率。


        使用线程池的好处: 

        1.减少在创建和销毁线程上所花的时间以及系统资源的开销 
        2.如不使用线程池,有可能造成系统创建大量线程而导致消耗完系统内存以及”过度切换”。 

    newFixedThreadPool和ThreadPoolExecutor源码分析

    Executors

      |----newFixedThreadPool

          |----ThreadPoolExecutor

    Java1.5中引入的Executor框架把任务的提交和执行进行解耦,只需要定义好任务,然后提交给线程池,而不用关心该任务是如何执行、被哪个线程执行,以及什么时候执行。

    线程池框架Executor的简单使用,负责执行任务的线程的生命周期都由Executor框架进行管理;:

     1 import java.util.concurrent.Executor;
     2 import java.util.concurrent.Executors;
     3 
     4 public class ExecutorCase {
        //Executors.newFixedThreadPool(10)初始化一个包含10个线程的线程池executor;
    5 private static Executor executor = Executors.newFixedThreadPool(10); 6 public static void main(String[] args){ 7 for(int i =0;i<10;i++){
             //
    通过executor.execute方法提交20个任务,每个任务打印当前的线程名;
    8         executor.execute(new task());
    9
    }
    10 }
    11 static class task implements Runnable{
    12 public void run(){
    13 System.out.println(Thread.currentThread().getName());
    14 }
    15 }
    16 }

     Executors.newFixedThreadPool()方法调用了ThreadPoolExecutor()方法,由源码可知。

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

    Executors是java线程池的工厂类,通过它可以快速初始化一个符合业务需求的线程池,如Executors.newFixedThreadPool方法可以生成一个拥有固定线程数的线程池。其本质是通过不同的参数初始化一个ThreadPoolExecutor对象。下面看ThreadPoolExecutor方法的源码。

     1 public ThreadPoolExecutor(int corePoolSize,  
     2                            int maximumPoolSize,  
     3                            long keepAliveTime,  
     4                            TimeUnit unit,  
     5                            BlockingQueue<Runnable> workQueue,  
     6                            ThreadFactory threadFactory,  
     7                            RejectedExecutionHandler handler) {  
     8      if (corePoolSize < 0 ||  
     9          maximumPoolSize <= 0 ||  
    10          maximumPoolSize < corePoolSize ||  
    11          keepAliveTime < 0)  
    12          throw new IllegalArgumentException();  
    13      if (workQueue == null || threadFactory == null || handler == null)  
    14          throw new NullPointerException();  
    15      this.corePoolSize = corePoolSize;  
    16      this.maximumPoolSize = maximumPoolSize;  
    17      this.workQueue = workQueue;  
    18      this.keepAliveTime = unit.toNanos(keepAliveTime);  
    19      this.threadFactory = threadFactory;  
    20      this.handler = handler;  
    21  }  

     对其参数进行解析:

    corePoolSize

    线程池中的核心线程数,当提交一个任务时,线程池创建一个新线程执行任务,直到当前线程数等于corePoolSize;如果当前线程数为corePoolSize,继续提交的任务被保存到阻塞队列中,等待被执行;如果执行了线程池的prestartAllCoreThreads()方法,线程池会提前创建并启动所有核心线程。

    maximumPoolSize

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

    keepAliveTime

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

    unit

    keepAliveTime的单位;

    workQueue

    用来保存等待被执行的任务的阻塞队列,且任务必须实现Runable接口,在JDK中提供了如下阻塞队列:
    1、ArrayBlockingQueue:基于数组结构的有界阻塞队列,按FIFO排序任务;
    2、LinkedBlockingQuene:基于链表结构的阻塞队列,按FIFO排序任务,吞吐量通常要高于ArrayBlockingQuene;
    3、SynchronousQuene:一个不存储元素的阻塞队列,每个插入操作必须等到另一个线程调用移除操作,否则插入操作一直处于阻塞状态,吞吐量通常要高于LinkedBlockingQuene;
    4、priorityBlockingQuene:具有优先级的无界阻塞队列;

    threadFactory

    创建线程的工厂,通过自定义的线程工厂可以给每个新建的线程设置一个具有识别度的线程名。调用DefaultThreadFactory()构造方法

     1   static class DefaultThreadFactory implements ThreadFactory {
     2         private static final AtomicInteger poolNumber = new AtomicInteger(1);
     3         private final ThreadGroup group;
     4         private final AtomicInteger threadNumber = new AtomicInteger(1);
     5         private final String namePrefix;
     6 
     7         DefaultThreadFactory() {
     8             SecurityManager s = System.getSecurityManager();
     9             group = (s != null) ? s.getThreadGroup() :
    10                                   Thread.currentThread().getThreadGroup();
    11             namePrefix = "pool-" +
    12                           poolNumber.getAndIncrement() +
    13                          "-thread-";
    14         }
    15 
    16         public Thread newThread(Runnable r) {
    17             Thread t = new Thread(group, r,
    18                                   namePrefix + threadNumber.getAndIncrement(),
    19                                   0);
    20             if (t.isDaemon())
    21                 t.setDaemon(false);
    22             if (t.getPriority() != Thread.NORM_PRIORITY)
    23                 t.setPriority(Thread.NORM_PRIORITY);
    24             return t;
    25         }
    26     }

    handler

    线程池的饱和策略,当阻塞队列满了,且没有空闲的工作线程,如果继续提交任务,必须采取一种策略处理该任务,线程池提供了4种策略:
    1、AbortPolicy:直接抛出异常,默认策略;
    2、CallerRunsPolicy:用调用者所在的线程来执行任务;
    3、DiscardOldestPolicy:丢弃阻塞队列中靠最前的任务,并执行当前任务;
    4、DiscardPolicy:直接丢弃任务;
    当然也可以根据应用场景实现RejectedExecutionHandler接口,自定义饱和策略,如记录日志或持久化存储不能处理的任务。

    Executors线程池的初始化接口

    1. newFixedThreadPool: 初始化一个指定线程数的线程池,其中corePoolSize == maximumPoolSize,使用LinkedBlockingQuene作为阻塞队列,不过当线程池没有可执行任务时,也不会释放线程。
    2. newCachedThreadPool:初始化一个可以缓存线程的线程池,默认缓存60s,线程池的线程数可达到Integer.MAX_VALUE,即2147483647,内部使用SynchronousQueue作为阻塞队列;newCachedThreadPool在没有任务执行时,当线程的空闲时间超过keepAliveTime,会自动释放线程资源,当提交新任务时,如果没有空闲线程,则创建新线程执行任务,会导致一定的系统开销;
    3. newSingleThreadExecutor:初始化的线程池中只有一个线程,如果该线程异常结束,会重新创建一个新的线程继续执行任务,唯一的线程可以保证所提交任务的顺序执行,内部使用LinkedBlockingQueue作为阻塞队列。
    4. newScheduledThreadPool:初始化的线程池可以在指定的时间内周期性的执行所提交的任务,在实际的业务场景中可以使用该线程池定期的同步数据。

    内部实现原理

    除了newScheduledThreadPool的内部实现特殊一点之外,其它几个线程池都是基于ThreadPoolExecutor类实现的。

    线程池内部状态

     ThreadPoolExecutor线程池有5个状态,分别是:

    1.  RUNNING:可以接受新的任务,也可以处理阻塞队列里的任务
    2. SHUTDOWN:不接受新的任务,但是可以处理阻塞队列里的任务
    3. STOP:不接受新的任务,不处理阻塞队列里的任务,中断正在处理的任务
    4. TIDYING:过渡状态,也就是说所有的任务都执行完了,当前线程池已经没有有效的线程,这个时候线程池的状态将会TIDYING,并且将要调用terminated方法
    5. TERMINATED:终止状态。terminated方法调用完成以后的状态

     状态之间可以进行转换:

     RUNNING -> SHUTDOWN:手动调用shutdown方法,或者ThreadPoolExecutor要被GC回收的时候调用finalize方法,finalize方法内部也会调用shutdown方法

     (RUNNING or SHUTDOWN) -> STOP:调用shutdownNow方法

     SHUTDOWN -> TIDYING:当队列和线程池都为空的时候

     STOP -> TIDYING:当线程池为空的时候

     TIDYING -> TERMINATED:terminated方法调用完成之后

     ThreadPoolExecutor内部还保存着线程池的有效线程个数。

     任务提交

    线程池框架提供了两种方式提交任务,根据不同的业务需求选择不同的方式。

    1.Executor.execute():通过Executor.execute()方法提交的任务,必须实现Runnable接口,该方式提交的任务不能获取返回值,因此无法判断任务是否执行成功。 

    1      * @param command the runnable task
    2      * @throws RejectedExecutionException if this task cannot be
    3      * accepted for execution
    4      * @throws NullPointerException if command is null
    5      */
    6     void execute(Runnable command);

     2.submit() :通过ExecutorService.submit()方法提交的任务,可以获取任务执行完的返回值。

    1 public Future<?> submit(Runnable task) {
    2     if (task == null) throw new NullPointerException();
    3     RunnableFuture<Void> ftask = newTaskFor(task, null);
    4     execute(ftask);
    5     return ftask;
    6 }

    很明显地看到,submit方法内部使用了execute方法,而且submit方法是有返回值的。在调用execute方法之前,使用FutureTask包装一个Runnable,这个FutureTask就是返回值。

    由于submit方法内部调用execute方法,所以execute方法就是执行任务的方法,来看一下execute方法,execute方法内部分4个步骤进行处理。

    1. 如果当前正在执行的Worker(线程)数量比corePoolSize(基本大小)要小。直接创建一个新的Worker执行任务,会调用addWorker方法
    2. 如果当前正在执行的Worker数量大于等于corePoolSize(基本大小)。将任务放到阻塞队列里,如果阻塞队列没满并且状态是RUNNING的话,直接丢到阻塞队列,否则执行第4步。
    3. 丢到阻塞队列之后,还需要再做一次验证(丢到阻塞队列之后可能另外一个线程关闭了线程池或者刚刚加入到队列的线程死了)。如果这个时候线程池不在RUNNING状态,把刚刚丢入队列的任务remove掉,调用reject方法,否则查看Worker数量,如果Worker数量为0,起一个新的Worker去阻塞队列里拿任务执行
    4. 丢到阻塞失败的话,会调用addWorker方法尝试起一个新的Worker去阻塞队列拿任务并执行任务,如果这个新的Worker创建失败,调用reject方法

    execute()源码 

     1 public void execute(Runnable command) {
     2     if (command == null)
     3         throw new NullPointerException();
     4     int c = ctl.get();
     5     if (workerCountOf(c) < corePoolSize) {   // 第一个步骤,满足线程池中的线程大小比基本大小要小
     6         if (addWorker(command, true)) // addWorker方法第二个参数true表示使用基本大小
     7             return;
     8         c = ctl.get();
     9     }
    10     if (isRunning(c) && workQueue.offer(command)) { // 第二个步骤,线程池的线程大小比基本大小要大,并且线程池还在RUNNING状态,阻塞队列也没满的情况,加到阻塞队列里
    11         int recheck = ctl.get();
    12         if (! isRunning(recheck) && remove(command)) // 虽然满足了第二个步骤,但是这个时候可能突然线程池关闭了,所以再做一层判断
    13             reject(command);
    14         else if (workerCountOf(recheck) == 0)
    15             addWorker(null, false);
    16     }
    17     else if (!addWorker(command, false)) // 第三个步骤,直接使用线程池最大大小。addWorker方法第二个参数false表示使用最大大小
    18         reject(command);
    19 }

     addWorker关系着如何起一个线程,再看addWorker方法之前,先看一下ThreadPoolExecutor的一个内部类Worker, Worker是一个AQS的实现类(为何设计成一个AQS在闲置Worker里会说明),同时也是一个实现Runnable的类,使用独占锁,它的构造函数只接受一个Runnable参数,内部保存着这个Runnable属性,还有一个thread线程属性用于包装这个Runnable(这个thread属性使用ThreadFactory构造,在构造函数内完成thread线程的构造),另外还有一个completedTasks计数器表示这个Worker完成的任务数。Worker类复写了run方法,使用ThreadPoolExecutor的runWorker方法(在addWorker方法里调用),直接启动Worker的话,会调用ThreadPoolExecutor的runWork方法。需要特别注意的是这个Worker是实现了Runnable接口的,thread线程属性使用ThreadFactory构造Thread的时候,构造的Thread中使用的Runnable其实就是Worker。

    下面的Worker的源码:

     1 private final class Worker    
     2     extends AbstractQueuedSynchronizer
     3     implements Runnable
     4 {
     5     /**
     6      * This class will never be serialized, but we provide a
     7      * serialVersionUID to suppress a javac warning.
     8      */
     9     private static final long serialVersionUID = 6138294804551838833L;
    10 
    11     /** Thread this worker is running in.  Null if factory fails. */
    12     final Thread thread;
    13     /** Initial task to run.  Possibly null. */
    14     Runnable firstTask;
    15     /** Per-thread task counter */
    16     volatile long completedTasks;
    17 
    18     /**
    19      * Creates with given first task and thread from ThreadFactory.
    20      * @param firstTask the first task (null if none)
    21      */
    22     Worker(Runnable firstTask) {  //它的构造函数只接受一个Runnable参数
    23         // 使用ThreadFactory构造Thread,这个构造的Thread内部的Runnable就是本身,也就是Worker。所以得到Worker的thread并start的时候,
            //会执行Worker的run方法,也就是执行ThreadPoolExecutor的runWorker方法
    24 setState(-1); //把状态位设置成-1,这样任何线程都不能得到Worker的锁,除非调用了unlock方法。这个unlock方法会在runWorker方法中一开始就调用,
                    //这是为了确保Worker构造出来之后,没有任何线程能够得到它的锁,除非调用了runWorker之后,其他线程才能获得Worker的锁
    25 this.firstTask = firstTask; 26 this.thread = getThreadFactory().newThread(this); 27 } 28 29 /** Delegates main run loop to outer runWorker */ 30 public void run() { 31 runWorker(this); 32 } 33 34 // Lock methods 35 // 36 // The value 0 represents the unlocked state. 37 // The value 1 represents the locked state. 38 39 protected boolean isHeldExclusively() { 40 return getState() != 0; 41 } 42 43 protected boolean tryAcquire(int unused) { 44 if (compareAndSetState(0, 1)) { 45 setExclusiveOwnerThread(Thread.currentThread()); 46 return true; 47 } 48 return false; 49 } 50 51 protected boolean tryRelease(int unused) { 52 setExclusiveOwnerThread(null); 53 setState(0); 54 return true; 55 } 56 57 public void lock() { acquire(1); } 58 public boolean tryLock() { return tryAcquire(1); } 59 public void unlock() { release(1); } 60 public boolean isLocked() { return isHeldExclusively(); } 61 62 void interruptIfStarted() { 63 Thread t; 64 if (getState() >= 0 && (t = thread) != null && !t.isInterrupted()) { 65 try { 66 t.interrupt(); 67 } catch (SecurityException ignore) { 68 } 69 } 70 } 71 }

    接下来看一下addWorker源码:

     1 // 两个参数,firstTask表示需要跑的任务。boolean类型的core参数为true的话表示使用线程池的基本大小,为false使用线程池最大大小
     2 // 返回值是boolean类型,true表示新任务被接收了,并且执行了。否则是false
     3 private boolean addWorker(Runnable firstTask, boolean core) {
     4     retry:
     5     for (;;) {
     6         int c = ctl.get();
     7         int rs = runStateOf(c); // 线程池当前状态
     8 
     9         // 这个判断转换成 rs >= SHUTDOWN && (rs != SHUTDOWN || firstTask != null || workQueue.isEmpty)。 
    10         // 概括为3个条件:
    11         // 1. 线程池不在RUNNING状态并且状态是STOP、TIDYING或TERMINATED中的任意一种状态
    12 
    13         // 2. 线程池不在RUNNING状态,线程池接受了新的任务 
    14 
    15         // 3. 线程池不在RUNNING状态,阻塞队列为空。  满足这3个条件中的任意一个的话,拒绝执行任务
    16 
    17         if (rs >= SHUTDOWN &&
    18             ! (rs == SHUTDOWN &&
    19                firstTask == null &&
    20                ! workQueue.isEmpty()))
    21             return false;
    22 
    23         for (;;) {
    24             int wc = workerCountOf(c); // 线程池线程个数
    25             if (wc >= CAPACITY ||
    26                 wc >= (core ? corePoolSize : maximumPoolSize)) // 如果线程池线程数量超过线程池最大容量或者线程数量超过了基本大小(core参数为true,core参数为false的话判断超过最大大小)
    27                 return false; // 超过直接返回false
    28             if (compareAndIncrementWorkerCount(c)) // 没有超过各种大小的话,cas操作线程池线程数量+1,cas成功的话跳出循环
    29                 break retry;
    30             c = ctl.get();  // 重新检查状态
    31             if (runStateOf(c) != rs) // 如果状态改变了,重新循环操作
    32                 continue retry;
    33             // else CAS failed due to workerCount change; retry inner loop
    34         }
    35     }
    36     // 走到这一步说明cas操作成功了,线程池线程数量+1
    37     boolean workerStarted = false; // 任务是否成功启动标识
    38     boolean workerAdded = false; // 任务是否添加成功标识
    39     Worker w = null;
    40     try {
    41         final ReentrantLock mainLock = this.mainLock; // 得到线程池的可重入锁
    42         w = new Worker(firstTask); // 基于任务firstTask构造worker
    43         final Thread t = w.thread; // 使用Worker的属性thread,这个thread是使用ThreadFactory构造出来的
    44         if (t != null) { // ThreadFactory构造出的Thread有可能是null,做个判断
    45             mainLock.lock(); // 锁住,防止并发
    46             try {
    47                 // 在锁住之后再重新检测一下状态
    48                 int c = ctl.get();
    49                 int rs = runStateOf(c);
    50 
    51                 if (rs < SHUTDOWN ||
    52                     (rs == SHUTDOWN && firstTask == null)) { // 如果线程池在RUNNING状态或者线程池在SHUTDOWN状态并且任务是个null
    53                     if (t.isAlive()) // 判断线程是否还活着,也就是说线程已经启动并且还没死掉
    54                         throw new IllegalThreadStateException(); // 如果存在已经启动并且还没死的线程,抛出异常
    55                     workers.add(w); // worker添加到线程池的workers属性中,是个HashSet
    56                     int s = workers.size(); // 得到目前线程池中的线程个数
    57                     if (s > largestPoolSize) // 如果线程池中的线程个数超过了线程池中的最大线程数时,更新一下这个最大线程数
    58                         largestPoolSize = s;
    59                     workerAdded = true; // 标识一下任务已经添加成功
    60                 }
    61             } finally {
    62                 mainLock.unlock(); // 解锁
    63             }
    64             if (workerAdded) { // 如果任务添加成功,运行任务,改变一下任务成功启动标识
    65                 t.start(); // 启动线程,这里的t是Worker中的thread属性,所以相当于就是调用了Worker的run方法
    66                 workerStarted = true;
    67             }
    68         }
    69     } finally {
    70         if (! workerStarted) // 如果任务启动失败,调用addWorkerFailed方法
    71             addWorkerFailed(w);
    72     }
    73     return workerStarted;
    74 }

    Worker中的线程start的时候,调用Worker本身run方法,这个run方法之前分析过,调用外部类ThreadPoolExecutor的runWorker方法,直接看runWorker方法:

     1 final void runWorker(Worker w) {
     2     Thread wt = Thread.currentThread(); // 得到当前线程
     3     Runnable task = w.firstTask; // 得到Worker中的任务task,也就是用户传入的task
     4     w.firstTask = null; // 将Worker中的任务置空
     5     w.unlock(); // allow interrupts。 
     6     boolean completedAbruptly = true;
     7     try {
     8         // 如果worker中的任务不为空,继续知否,否则使用getTask获得任务。一直死循环,除非得到的任务为空才退出
     9         while (task != null || (task = getTask()) != null) {
    10             w.lock();  // 如果拿到了任务,给自己上锁,表示当前Worker已经要开始执行任务了,已经不是闲置Worker(闲置Worker的解释请看下面的线程池关闭)
    11             // 在执行任务之前先做一些处理。 1. 如果线程池已经处于STOP状态并且当前线程没有被中断,中断线程 2. 如果线程池还处于RUNNING或SHUTDOWN状态,并且当前线程已经被中断了,重新检查一下线程池状态,如果处于STOP状态并且没有被中断,那么中断线程
    12             if ((runStateAtLeast(ctl.get(), STOP) ||
    13                  (Thread.interrupted() &&
    14                   runStateAtLeast(ctl.get(), STOP))) &&
    15                 !wt.isInterrupted())
    16                 wt.interrupt();
    17             try {
    18                 beforeExecute(wt, task); // 任务执行前需要做什么,ThreadPoolExecutor是个空实现
    19                 Throwable thrown = null;
    20                 try {
    21                     task.run(); // 真正的开始执行任务,调用的是run方法,而不是start方法。这里run的时候可能会被中断,比如线程池调用了shutdownNow方法
    22                 } catch (RuntimeException x) { // 任务执行发生的异常全部抛出,不在runWorker中处理
    23                     thrown = x; throw x;
    24                 } catch (Error x) {
    25                     thrown = x; throw x;
    26                 } catch (Throwable x) {
    27                     thrown = x; throw new Error(x);
    28                 } finally {
    29                     afterExecute(task, thrown); // 任务执行结束需要做什么,ThreadPoolExecutor是个空实现
    30                 }
    31             } finally {
    32                 task = null;
    33                 w.completedTasks++; // 记录执行任务的个数
    34                 w.unlock(); // 执行完任务之后,解锁,Worker变成闲置Worker
    35             }
    36         }
    37         completedAbruptly = false;
    38     } finally {
    39         processWorkerExit(w, completedAbruptly); // 回收Worker方法
    40     }
    41 }

    getTask方法是如何获得任务的:

     1 // 如果发生了以下四件事中的任意一件,那么Worker需要被回收:
     2 // 1. Worker个数比线程池最大大小要大
     3 // 2. 线程池处于STOP状态
     4 // 3. 线程池处于SHUTDOWN状态并且阻塞队列为空
     5 // 4. 使用超时时间从阻塞队列里拿数据,并且超时之后没有拿到数据(allowCoreThreadTimeOut || workerCount > corePoolSize)
     6 private Runnable getTask() {
     7     boolean timedOut = false; // 如果使用超时时间并且也没有拿到任务的标识
     8 
     9     retry:
    10     for (;;) {
    11         int c = ctl.get();
    12         int rs = runStateOf(c);
    13 
    14         // 如果线程池是SHUTDOWN状态并且阻塞队列为空的话,worker数量减一,直接返回null(SHUTDOWN状态还会处理阻塞队列任务,但是阻塞队列为空的话就结束了),如果线程池是STOP状态的话,worker数量建议,直接返回null(STOP状态不处理阻塞队列任务)[方法一开始注释的2,3两点,返回null,开始Worker回收]
    15         if (rs >= SHUTDOWN && (rs >= STOP || workQueue.isEmpty())) {
    16             decrementWorkerCount();
    17             return null;
    18         }
    19 
    20         boolean timed;      // 标记从队列中取任务时是否设置超时时间,如果为true说明这个worker可能需要回收,为false的话这个worker会一直存在,并且阻塞当前线程等待阻塞队列中有数据
    21 
    22         for (;;) {
    23             int wc = workerCountOf(c); // 得到当前线程池Worker个数
    24             // allowCoreThreadTimeOut属性默认为false,表示线程池中的核心线程在闲置状态下还保留在池中;如果是true表示核心线程使用keepAliveTime这个参数来作为超时时间
    25             // 如果worker数量比基本大小要大的话,timed就为true,需要进行回收worker
    26             timed = allowCoreThreadTimeOut || wc > corePoolSize; 
    27 
    28             if (wc <= maximumPoolSize && ! (timedOut && timed)) // 方法一开始注释的1,4两点,会进行下一步worker数量减一
    29                 break;
    30             if (compareAndDecrementWorkerCount(c)) // worker数量减一,返回null,之后会进行Worker回收工作
    31                 return null;
    32             c = ctl.get();  // 重新检查线程池状态
    33             if (runStateOf(c) != rs) // 线程池状态改变的话重新开始外部循环,否则继续内部循环
    34                 continue retry;
    35  // else CAS failed due to workerCount change; retry inner loop
    36         }
    37 
    38         try {
    39             // 如果需要设置超时时间,使用poll方法,否则使用take方法一直阻塞等待阻塞队列新进数据
    40             Runnable r = timed ?
    41                 workQueue.poll(keepAliveTime, TimeUnit.NANOSECONDS) :
    42                 workQueue.take();
    43             if (r != null)
    44                 return r;
    45             timedOut = true;
    46         } catch (InterruptedException retry) {
    47             timedOut = false; // 闲置Worker被中断
    48         }
    49     }
    50 }

    如果getTask返回的是null,那说明阻塞队列已经没有任务并且当前调用getTask的Worker需要被回收,那么会调用processWorkerExit方法进行回收:

     1 private void processWorkerExit(Worker w, boolean completedAbruptly) {
     2     if (completedAbruptly) // 如果Worker没有正常结束流程调用processWorkerExit方法,worker数量减一。如果是正常结束的话,在getTask方法里worker数量已经减一了
     3         decrementWorkerCount();
     4 
     5     final ReentrantLock mainLock = this.mainLock;
     6     mainLock.lock(); // 加锁,防止并发问题
     7     try {
     8         completedTaskCount += w.completedTasks; // 记录总的完成任务数
     9         workers.remove(w); // 线程池的worker集合删除掉需要回收的Worker
    10     } finally {
    11         mainLock.unlock(); // 解锁
    12     }
    13 
    14     tryTerminate(); // 尝试结束线程池
    15 
    16     int c = ctl.get();
    17     if (runStateLessThan(c, STOP)) {  // 如果线程池还处于RUNNING或者SHUTDOWN状态
    18         if (!completedAbruptly) { // Worker是正常结束流程的话
    19             int min = allowCoreThreadTimeOut ? 0 : corePoolSize;
    20             if (min == 0 && ! workQueue.isEmpty())
    21                 min = 1;
    22             if (workerCountOf(c) >= min)
    23                 return; // 不需要新开一个Worker
    24         }
    25         // 新开一个Worker代替原先的Worker
    26         // 新开一个Worker需要满足以下3个条件中的任意一个:
    27         // 1. 用户执行的任务发生了异常
    28         // 2. Worker数量比线程池基本大小要小
    29         // 3. 阻塞队列不空但是没有任何Worker在工作
    30         addWorker(null, false);
    31     }
    32 }

    在回收Worker的时候线程池会尝试结束自己的运行,tryTerminate方法:

     1 final void tryTerminate() {
     2     for (;;) {
     3         int c = ctl.get();
     4         // 满足3个条件中的任意一个,不终止线程池
     5         // 1. 线程池还在运行,不能终止
     6         // 2. 线程池处于TIDYING或TERMINATED状态,说明已经在关闭了,不允许继续处理
     7         // 3. 线程池处于SHUTDOWN状态并且阻塞队列不为空,这时候还需要处理阻塞队列的任务,不能终止线程池
     8         if (isRunning(c) ||
     9             runStateAtLeast(c, TIDYING) ||
    10             (runStateOf(c) == SHUTDOWN && ! workQueue.isEmpty()))
    11             return;
    12         // 走到这一步说明线程池已经不在运行,阻塞队列已经没有任务,但是还要回收正在工作的Worker
    13         if (workerCountOf(c) != 0) {
    14              // 由于线程池不运行了,调用了线程池的关闭方法,在解释线程池的关闭原理的时候会说道这个方法
    15             interruptIdleWorkers(ONLY_ONE); // 中断闲置Worker,直到回收全部的Worker。这里没有那么暴力,只中断一个,中断之后退出方法,中断了Worker之后,Worker会回收,然后还是会调用tryTerminate方法,如果还有闲置线程,那么继续中断
    16             return;
    17         }
    18         // 走到这里说明worker已经全部回收了,并且线程池已经不在运行,阻塞队列已经没有任务。可以准备结束线程池了
    19         final ReentrantLock mainLock = this.mainLock;
    20         mainLock.lock(); // 加锁,防止并发
    21         try {
    22             if (ctl.compareAndSet(c, ctlOf(TIDYING, 0))) { // cas操作,将线程池状态改成TIDYING
    23                 try {
    24                     terminated(); // 调用terminated方法
    25                 } finally {
    26                     ctl.set(ctlOf(TERMINATED, 0)); // terminated方法调用完毕之后,状态变为TERMINATED
    27                     termination.signalAll();
    28                 }
    29                 return;
    30             }
    31         } finally {
    32             mainLock.unlock(); // 解锁
    33         }
    34         // else retry on failed CAS
    35     }
    36

    解释了这么多,对线程池的启动并且执行任务做一个总结:

    线程池构造完毕之后,如果用户调用了execute或者submit方法的时候,最后都会使用execute方法执行。

    execute方法内部分3种情况处理任务:

    1. 如果当前正在执行的Worker数量比corePoolSize(基本大小)要小。直接创建一个新的Worker执行任务,会调用addWorker方法
    2. 如果当前正在执行的Worker数量大于等于corePoolSize(基本大小)。将任务放到阻塞队列里,如果阻塞队列没满并且状态是RUNNING的话,直接丢到阻塞队列,否则执行第3步
    3. 丢到阻塞失败的话,会调用addWorker方法尝试起一个新的Worker去阻塞队列拿任务并执行任务,如果这个新的Worker创建失败,调用reject方法

    线程池中的这个基本大小指的是Worker的数量。一个Worker是一个Runnable的实现类,会被当做一个线程进行启动。Worker内部带有一个Runnable属性firstTask,这个firstTask可以为null,为null的话Worker会去阻塞队列拿任务执行,否则会先执行这个任务,执行完毕之后再去阻塞队列继续拿任务执行。

    所以说如果Worker数量超过了基本大小,那么任务都会在阻塞队列里,当Worker执行完了它的第一个任务之后,就会去阻塞队列里拿其他任务继续执行。

    Worker在执行的时候会根据一些参数进行调节,比如Worker数量超过了线程池基本大小或者超时时间到了等因素,这个时候Worker会被线程池回收,线程池会尽量保持内部的Worker数量不超过基本大小。

    另外Worker执行任务的时候调用的是Runnable的run方法,而不是start方法,调用了start方法就相当于另外再起一个线程了。

    Worker在回收的时候会尝试终止线程池。尝试关闭线程池的时候,会检查是否还有Worker在工作,检查线程池的状态,没问题的话会将状态过度到TIDYING状态,之后调用terminated方法,terminated方法调用完成之后将线程池状态更新到TERMINATED。

     关闭线程池

     线程池的启动过程分析好了之后,接下来看线程池的关闭操作:

     shutdown方法,关闭线程池,关闭之后阻塞队列里的任务不受影响,会继续被Worker处理,但是新的任务不会被接受:

     1 public void shutdown() {
     2     final ReentrantLock mainLock = this.mainLock;
     3     mainLock.lock(); // 关闭的时候需要加锁,防止并发
     4     try {
     5         checkShutdownAccess(); // 检查关闭线程池的权限
     6         advanceRunState(SHUTDOWN); // 把线程池状态更新到SHUTDOWN
     7         interruptIdleWorkers(); // 中断闲置的Worker
     8         onShutdown(); // 钩子方法,默认不处理。ScheduledThreadPoolExecutor会做一些处理
     9     } finally {
    10         mainLock.unlock(); // 解锁
    11     }
    12     tryTerminate(); // 尝试结束线程池,上面已经分析过了
    13 }

    interruptIdleWorkers方法,注意,这个方法打断的是闲置Worker,打断闲置Worker之后,getTask方法会返回null,然后Worker会被回收。那什么是闲置Worker呢?

    闲置Worker是这样解释的:Worker运行的时候会去阻塞队列拿数据(getTask方法),拿的时候如果没有设置超时时间,那么会一直阻塞等待阻塞队列进数据,这样的Worker就被称为闲置Worker。由于Worker也是一个AQS,在runWorker方法里会有一对lock和unlock操作,这对lock操作是为了确保Worker不是一个闲置Worker。

    所以Worker被设计成一个AQS是为了根据Worker的锁来判断是否是闲置线程,是否可以被强制中断。

    下面我们看下interruptIdleWorkers方法:

     1 // 调用他的一个重载方法,传入了参数false,表示要中断所有的正在运行的闲置Worker,如果为true表示只打断一个闲置Worker
     2 private void interruptIdleWorkers() {
     3     interruptIdleWorkers(false);
     4 }
     5 
     6 private void interruptIdleWorkers(boolean onlyOne) {
     7     final ReentrantLock mainLock = this.mainLock;
     8     mainLock.lock(); // 中断闲置Worker需要加锁,防止并发
     9     try {
    10         for (Worker w : workers) { 
    11             Thread t = w.thread; // 拿到worker中的线程
    12             if (!t.isInterrupted() && w.tryLock()) { // Worker中的线程没有被打断并且Worker可以获取锁,这里Worker能获取锁说明Worker是个闲置Worker,在阻塞队列里拿数据一直被阻塞,没有数据进来。如果没有获取到Worker锁,说明Worker还在执行任务,不进行中断(shutdown方法不会中断正在执行的任务)
    13                 try {
    14                     t.interrupt();  // 中断Worker线程
    15                 } catch (SecurityException ignore) {
    16                 } finally {
    17                     w.unlock(); // 释放Worker锁
    18                 }
    19             }
    20             if (onlyOne) // 如果只打断1个Worker的话,直接break退出,否则,遍历所有的Worker
    21                 break;
    22         }
    23     } finally {
    24         mainLock.unlock(); // 解锁
    25     }
    26 }

    shutdown方法将线程池状态改成SHUTDOWN,线程池还能继续处理阻塞队列里的任务,并且会回收一些闲置的Worker。但是shutdownNow方法不一样,它会把线程池状态改成STOP状态,这样不会处理阻塞队列里的任务,也不会处理新的任务:

    // shutdownNow方法会有返回值的,返回的是一个任务列表,而shutdown方法没有返回值
    public List<Runnable> shutdownNow() {
        List<Runnable> tasks;
        final ReentrantLock mainLock = this.mainLock;
        mainLock.lock(); // shutdownNow操作也需要加锁,防止并发
        try {
            checkShutdownAccess(); // 检查关闭线程池的权限
            advanceRunState(STOP); // 把线程池状态更新到STOP
            interruptWorkers(); // 中断Worker的运行
            tasks = drainQueue();
        } finally {
            mainLock.unlock(); // 解锁
        }
        tryTerminate(); // 尝试结束线程池,上面已经分析过了
        return tasks;
    }

    shutdownNow的中断和shutdown方法不一样,调用的是interruptWorkers方法:

     1 private void interruptWorkers() {
     2     final ReentrantLock mainLock = this.mainLock;
     3     mainLock.lock(); // 中断Worker需要加锁,防止并发
     4     try {
     5         for (Worker w : workers)
     6             w.interruptIfStarted(); // 中断Worker的执行
     7     } finally {
     8         mainLock.unlock(); // 解锁
     9     }
    10 }

    Worker的interruptIfStarted方法中断Worker的执行:

     1 void interruptIfStarted() {
     2    Thread t;
     3    // Worker无论是否被持有锁,只要还没被中断,那就中断Worker
     4    if (getState() >= 0 && (t = thread) != null && !t.isInterrupted()) {
     5        try {
     6            t.interrupt(); // 强行中断Worker的执行
     7        } catch (SecurityException ignore) {
     8        }
     9    }
    10 }

    线程池关闭总结:

    线程池的关闭主要是两个方法,shutdown和shutdownNow方法。

    shutdown方法会更新状态到SHUTDOWN,不会影响阻塞队列里任务的执行,但是不会执行新进来的任务。同时也会回收闲置的Worker,闲置Worker的定义上面已经说过了。

    shutdownNow方法会更新状态到STOP,会影响阻塞队列的任务执行,也不会执行新进来的任务。同时会回收所有的Worker。

  • 相关阅读:
    JavaUtil_04_验证码生成器
    Java微信公众平台开发_02_启用服务器配置
    Log4j2_学习_01_Log4j 2使用教程
    Java_Time_01_获取当前时间
    Eclipse_配置_00_资源帖
    Eclipse_插件_02_jd-eclipse插件的安装
    noip模拟题题解集
    小结:高斯消元
    10月刷题总结
    【vijos】1892 树上的最大匹配(树形dp+计数)
  • 原文地址:https://www.cnblogs.com/jjfan0327/p/6844054.html
Copyright © 2011-2022 走看看