线程池源码解析

前言

  Java提供了若干种线程池,常用的线程池是ThreadPoolExecutor,其中用于提交任务的方法是execute以及submit方法,其中submit最终也是会调用execute方法,所以这里只介绍excute及其相关方法。

  在介绍源码之前,希望读者对于线程池参数有基本了解会有助于理解代码,可以查看这篇文章https://juejin.im/post/5cae9b42f265da03705fa152

主要成员变量

 1 // 线程池状态量,高3未用于表示线程池运行状态,低29位用于表示线程池的线程数
 2 private final AtomicInteger ctl = new AtomicInteger(ctlOf(RUNNING, 0));
 3 private static final int COUNT_BITS = Integer.SIZE - 3;
 4 // 线程池最大容量,这里是程序硬性规定的最大数量,优先级高于maximumPoolSize
 5 private static final int CAPACITY   = (1 << COUNT_BITS) - 1;
 6 
 7 // 线程池运行状态
 8 private static final int RUNNING    = -1 << COUNT_BITS;
 9 private static final int SHUTDOWN   =  0 << COUNT_BITS;
10 private static final int STOP       =  1 << COUNT_BITS;
11 private static final int TIDYING    =  2 << COUNT_BITS;
12 private static final int TERMINATED =  3 << COUNT_BITS;

  RUNNING:能够接受新的任务。

  SHUTDOWN:不接受新任务,但是会将已提交任务执行完。

  STOP:不接受新任务,并且不会执行已提交任务。

  TIDYING:当所有线程都终止时之后,线程池会变为该状态,当线程池变为该状态时会调用钩子函数terminated,该方法在线程池子是个空方法,可由程序员实现。

  TERMINATED:线程池彻底停止,线程状态变为TIDYING并且执行完terminated后,线程池会变为该状态。

  线程池状态切换流程:

主要源码分析

  首先是execute方法,该方法是公有方法,是提交任务的入口之一。

 1 public void execute(Runnable command) {
 2     if (command == null)
 3         throw new NullPointerException();
 4     
 5     // 获取线程状态量
 6     int c = ctl.get();
 7     // 1. 判断当前工作线程是否小于核心线程数
 8     if (workerCountOf(c) < corePoolSize) {
 9         // 创建工作线程
10         if (addWorker(command, true))
11             return;
12         c = ctl.get();
13     }
14     // 2. 线程池是否处于RUNNING状态 && 等待队列入队成功
15     if (isRunning(c) && workQueue.offer(command)) {
16         int recheck = ctl.get();
17         // double-check 防止在入队时有线程退出或线程关闭等情况
18         if (! isRunning(recheck) && remove(command))
19             reject(command);
20         // 如果工作线程数变为0,新建非核心线程执行该任务
21         else if (workerCountOf(recheck) == 0)
22             addWorker(null, false);
23     }
24     // 3. 创建额外的线程执行任务
25     else if (!addWorker(command, false))
26     // 执行拒绝策略
27         reject(command);
28 }

  1. 如果线程池中的线程小于corePoolSize,则创建新的工作线程

  (1) 如果创建工作线程失败,说明线程池核心线程已满

  2. 线程池的核心线程已满,则将新任务放到等待队列中

  (1) 如果任务入队之后线程池状态不是RUNNING且成功将其从等待队列中删除时,执行拒绝策略

  (2) 如果工作线程数变为0,新建非核心线程执行该任务

  3. 线程池核心线程已满,且等待队列已满,则试图创建非核心线程,如果创建失败说明线程池已达到最大线程数,执行拒绝策略

 

  然后是addWorker()方法,该方法是添加工作线程的核心方法

 1 private boolean addWorker(Runnable firstTask, boolean core) {
 2     retry:
 3     for (;;) {
 4         int c = ctl.get();
 5         int rs = runStateOf(c);
 6 
 7         // 1. 根据线程池状态判断是否能够新建工作线程
 8         if (rs >= SHUTDOWN &&
 9             ! (rs == SHUTDOWN &&
10                firstTask == null &&
11                ! workQueue.isEmpty()))
12             return false;
13 
14         for (;;) {
15             int wc = workerCountOf(c);
16             // 2. 判断当前线程池线程个数是否能够新建工作线程
17             if (wc >= CAPACITY ||
18                 wc >= (core ? corePoolSize : maximumPoolSize))
19                 return false;
20             // 尝试更新状态量
21             if (compareAndIncrementWorkerCount(c))
22                 break retry;
23             // 更新状态量失败,说明发生竞争,重试
24             c = ctl.get();  // Re-read ctl
25             if (runStateOf(c) != rs)
26                 continue retry;
27             // else CAS failed due to workerCount change; retry inner loop
28         }
29     }
30 
31     boolean workerStarted = false;
32     boolean workerAdded = false;
33     Worker w = null;
34     try {
35         // 3. 新建工作线程
36         w = new Worker(firstTask);
37         final Thread t = w.thread;
38         if (t != null) {
39             final ReentrantLock mainLock = this.mainLock;
40             mainLock.lock();
41             try {
42                 // Recheck while holding lock.
43                 // Back out on ThreadFactory failure or if
44                 // shut down before lock acquired.
45                 int rs = runStateOf(ctl.get());
46 
47                 if (rs < SHUTDOWN ||
48                     (rs == SHUTDOWN && firstTask == null)) {
49                     // 如果提交的任务线程已被其他方式启动,则报错
50                     if (t.isAlive()) 
51                         throw new IllegalThreadStateException();
52                     // 将当前线程添加到工作线程集中
53                     workers.add(w);
54                     int s = workers.size();
55                     // 更新线程数量
56                     if (s > largestPoolSize)
57                         largestPoolSize = s;
58                     workerAdded = true;
59                 }
60             } finally {
61                 mainLock.unlock();
62             }
63             if (workerAdded) {
64                 // 4. 启动线程
65                 t.start();
66                 workerStarted = true;
67             }
68         }
69     } finally {
70         if (! workerStarted)
71             addWorkerFailed(w);
72     }
73     return workerStarted;
74 }

  1.  首先判断线程池状态是否能够创建新线程,我们回过头看一下前面提到的文章状态,其中只有RUNNING状态下才会接受新的任务;但是SHUTDOWN状态下的线程池可能需要新的线程来执行还未执行的任务,所以SHUTDOWN状态且等待队列不为空的时候允许创建新线程并且启动的线程本身不能够携带新任务

  2. 判断线程数量是否超过规定的线程数

  3. 新建线程,加锁之后再次检查线程池状态是否能够创建线程;其条件与步骤1中的条件一致,创建成功之后就启动线程。

 

  在addWorker方法中出现了工作线程Worker对象,我们来看看这个对象的核心属性

 1 private final class Worker
 2     extends AbstractQueuedSynchronizer
 3     implements Runnable
 4 {
 5     // 核心属性,工作线程
 6     final Thread thread;
 7     // 第一个任务,可为null
 8     Runnable firstTask;
 9     // 已完成的任务计数
10     volatile long completedTasks;
11     // 构造方法
12     Worker(Runnable firstTask) {
13         setState(-1); // inhibit interrupts until runWorker
14         this.firstTask = firstTask;
15         this.thread = getThreadFactory().newThread(this);
16     }
17     
18     public void run() {
19         runWorker(this);
20     }
21 }    

  该类继承了AQS,用于在线程执行过程中能够中断线程;实现了Runnable接口,可以将自身作为任务在线程中运行。

  该类的构造方法持有一个任务,然后将自己作为任务创建一个线程,这就是Worker的核心。由此可见,在addWorker方法的步骤4启动的线程并非是提交的任务,而是Worker本身,Worker的run方法直接调用runWorker方法。

  让我们看看runWorker方法

 1 final void runWorker(Worker w) {
 2     Thread wt = Thread.currentThread();
 3     Runnable task = w.firstTask;
 4     w.firstTask = null;
 5     w.unlock(); // allow interrupts
 6     boolean completedAbruptly = true;
 7     try {
 8         // 1. 获取需要执行的任务
 9         while (task != null || (task = getTask()) != null) {
10             w.lock();
11             // 2. 判断是否需要中断线程
12             if ((runStateAtLeast(ctl.get(), STOP) ||
13                  (Thread.interrupted() &&
14                   runStateAtLeast(ctl.get(), STOP))) &&
15                 !wt.isInterrupted())
16                 wt.interrupt();
17             try {
18                 // 线程执行前执行的方法,钩子方法,程序员自行实现
19                 beforeExecute(wt, task);
20                 Throwable thrown = null;
21                 try {
22                     // 3. 执行任务
23                     task.run();
24                 } catch (RuntimeException x) {
25                     thrown = x; throw x;
26                 } catch (Error x) {
27                     thrown = x; throw x;
28                 } catch (Throwable x) {
29                     thrown = x; throw new Error(x);
30                 } finally {
31                     // 线程执行后执行的方法,钩子方法,程序员自行实现
32                     afterExecute(task, thrown);
33                 }
34             } finally {
35                 task = null;
36                 w.completedTasks++;
37                 w.unlock();
38             }
39         }
40         completedAbruptly = false;
41     } finally {
42         // 4. 线程退出前更新线程池状态
43         processWorkerExit(w, completedAbruptly);
44     }
45 }

  1. 获取执行的任务,其中getTask方法是一个核心方法,我们稍后介绍

  2. 根据线程池状态判断是否需要响应中断。当线程池状态变为STOP及以上状态时,为了确保当前线程能够被中断,所以进行线程中断操作(主要防止在线程池状态已经变为STOP,但是执行shutdownNow操作的线程还未中断当前线程而进行的必要操作);如果线程池不是STOP状态,则判断线程是否已经被中断(请注意,代码中调用的是interrupted方法,该方法除了返回线程是否中断之外,还会清除掉线程的中断标志位),如果未中断则再次判断线程池是否是STOP状态,如果是则响应中断。

  由此可以看出,在线程池状态为STOP状态以下时,外部操作是无法中断工作线程的,感兴趣还可以自己手动debug。

  3. 执行任务

  4. 如果未获取到任务,则退出循环,这里退出循环意味着线程将退出

  runWorker方法中有两个重要方法:getTask以及processWorkerExit,我们先讲getTask。

 1 private Runnable getTask() {
 2     // 获取任务超时标志
 3     boolean timedOut = false;
 4 
 5     // 循环退出即线程退出
 6     for (;;) {
 7         int c = ctl.get();
 8         int rs = runStateOf(c);
 9 
10         // 1. 线程池状态为STOP或者SHUTDOWN且等待队列为空时退出
11         if (rs >= SHUTDOWN && (rs >= STOP || workQueue.isEmpty())) {
12             // ctl-1
13             decrementWorkerCount();
14             return null;
15         }
16         // 获取工作线程数量
17         int wc = workerCountOf(c);
18         // 2.
19         // 是否定时,控制当前线程是否退出的重要标识
20         // 如果线程池允许线程超时 或者 工作线程数量大于核心线程数
21         boolean timed = allowCoreThreadTimeOut || wc > corePoolSize;
22         // 3.
23         // wc > maximumPoolSize 工作线程数大于最大线程数
24         // timed && timedOut 获取任务已超时
25         // wc > 1 至少又一个工作线程
26         // workQueue.isEmpty() 等待队列为空
27         if ((wc > maximumPoolSize || (timed && timedOut))
28             && (wc > 1 || workQueue.isEmpty())) {
29             if (compareAndDecrementWorkerCount(c))
30                 return null;
31             continue;
32         }
33 
34         try {
35             // 4.
36             // poll方法是从等待队列中取一个任务,超时时间为keepAliveTime
37             // take方法是从等待队列中取一个任务,没有超时时间
38             Runnable r = timed ?
39                 workQueue.poll(keepAliveTime, TimeUnit.NANOSECONDS) :
40                 workQueue.take();
41             if (r != null)
42                 return r;
43             timedOut = true;
44         } catch (InterruptedException retry) {
45             timedOut = false;
46         }
47     }
48 }

  1. 文章开始提到的线程池不同状态的不同行为

  2. allowCoreThreadTimeOut是线程池配置;当工作线程数大于核心线程数时,timed同样为true,timed为true会使没有从等待队列中获取不到任务的线程退出,这就保证了线程池在空闲状态下维持corePoolSize个工作线程

  3. 判断当前线程是否退出的核心代码,wc > 1 || workeQueue.isEmpty()这段代码保证了等待队列不不为空的情况下线程池至少有1个线程,当工作线程数大于线程池最大线程数或线程获取任务超时且当前线数大于核心线程数时(wc > maximumPoolSize || (timed && timedOut)为真。

  4. 从等待队列中获取条件,根据timed表示通过不同的方式获取任务

  该方法是线程池的核心方法之一,它完成了任务的调度和工作线程数的维护两个核心功能。

 1 private void processWorkerExit(Worker w, boolean completedAbruptly) {
 2     // 当线程因为异常而退出的时候为true
 3     if (completedAbruptly)
 4         // ctl减一
 5         decrementWorkerCount();
 6 
 7     final ReentrantLock mainLock = this.mainLock;
 8     mainLock.lock();
 9     try {
10         completedTaskCount += w.completedTasks;
11         // 从线程hashSet中移除
12         workers.remove(w);
13     } finally {
14         mainLock.unlock();
15     }
16 
17     // 尝试将线程池状态变为terminate
18     tryTerminate();
19 
20     int c = ctl.get();
21     // 如果线程池状态小于STOP
22     if (runStateLessThan(c, STOP)) {
23         // 如果线程不是异常结束
24         if (!completedAbruptly) {
25             // 如果允许线程超时且等待队列不为空,则至少需要1个线程执行等待任务
26             // 如果线程不允许等待队列,则线程池最小线程数为核心线程数
27             int min = allowCoreThreadTimeOut ? 0 : corePoolSize;
28             if (min == 0 && ! workQueue.isEmpty())
29                 min = 1;
30             if (workerCountOf(c) >= min)
31                 return; // replacement not needed
32         }
33         // 新增工作线程来执行剩余的任务
34         addWorker(null, false);
35     }
36 }

  以上便是ThreadPoolExecutor的主要流程代码,线程池的应用范围极广,在我看来线程池的本质思想与MQ相似,都可以概括为通过减少资源的使用者从而达到减轻服务器压力的目的。MQ的作用之一削峰就是通过Broken调度message来限制消费者的并发数从而达到限制资源的使用者数量;线程池通过限制线程数来达到资源的使用者数量。

  线程池并不是百利无一害,池化之后会带来一些问题。例如,由于线程池的线程是复用的,所以可能会导致ThreadLocal出现问题等。

 

posted @ 2020-02-26 11:17  随花四散  阅读(559)  评论(0编辑  收藏  举报