继续浏览精彩内容
慕课网APP
程序员的梦工厂
打开
继续
感谢您的支持,我会继续努力的
赞赏金额会直接到老师账户
将二维码发送给自己后长按识别
微信支付
支付宝支付

并发编程(十一)—— Java 线程池 实现原理与源码深度解析(一)

MMMHUHU
关注TA
已关注
手记 134
粉丝 26
获赞 98

正文

史上最清晰的线程池源码分析

鼎鼎大名的线程池。不需要多说!!!!!

这篇博客深入分析 Java 中线程池的实现。

回到顶部

总览

下图是 java 线程池几个相关类的继承结构:

  

先简单说说这个继承结构,Executor 位于最顶层,也是最简单的,就一个 execute(Runnable runnable) 接口方法定义。

ExecutorService 也是接口,在 Executor 接口的基础上添加了很多的接口方法,所以一般来说我们会使用这个接口。

然后再下来一层是 AbstractExecutorService,从名字我们就知道,这是抽象类,这里实现了非常有用的一些方法供子类直接使用,之后我们再细说。

然后才到我们的重点部分 ThreadPoolExecutor 类,这个类提供了关于线程池所需的非常丰富的功能。

线程池中的 BlockingQueue 也是非常重要的概念,如果线程数达到 corePoolSize,我们的每个任务会提交到等待队列中,等待线程池中的线程来取任务并执行。这里的 BlockingQueue 通常我们使用其实现类 LinkedBlockingQueue、ArrayBlockingQueue 和 SynchronousQueue,每个实现类都有不同的特征,使用场景之后会慢慢分析。想要详细了解各个 BlockingQueue 的读者,可以参考我的前面的一篇对 BlockingQueue 的各个实现类进行详细分析的文章。

回到顶部

使用示例

复制代码

 1 package main.java.Juc; 2  3 import java.util.concurrent.ExecutorService; 4 import java.util.concurrent.Executors; 5  6 class MyRunnable implements Runnable { 7     @Override 8     public void run() { 9         for (int x = 0; x < 100; x++) {10             System.out.println(Thread.currentThread().getName() + ":" + x);11         }12     }13 }14 15 public class TestThreadPool {16     public static void main(String[] args) {17         // 创建一个线程池对象,控制要创建几个线程对象。18         ExecutorService pool = Executors.newFixedThreadPool(2);19 20         // 可以执行Runnable对象或者Callable对象代表的线程21         pool.execute(new MyRunnable());22         pool.execute(new MyRunnable());23 24         //结束线程池25         pool.shutdown();26     }27 }

复制代码

运行结果:

回到顶部

Executor 接口

1 public interface Executor {2     void execute(Runnable command);3 }

我们可以看到 Executor 接口非常简单,就一个 void execute(Runnable command) 方法,代表提交一个任务。

 当然了,Executor 这个接口只有提交任务的功能,太简单了,我们想要更丰富的功能,比如我们想知道执行结果、我们想知道当前线程池有多少个线程活着、已经完成了多少任务等等,这些都是这个接口的不足的地方。接下来我们要介绍的是继承自 Executor 接口的 ExecutorService 接口,这个接口提供了比较丰富的功能,也是我们最常使用到的接口。

回到顶部

ExecutorService

那么我们简单初略地来看一下这个接口中都有哪些方法:

复制代码

 1 public interface ExecutorService extends Executor { 2  3     // 关闭线程池,已提交的任务继续执行,不接受继续提交新任务 4     void shutdown(); 5  6     // 关闭线程池,尝试停止正在执行的所有任务,不接受继续提交新任务 7     // 它和前面的方法相比,加了一个单词“now”,区别在于它会去停止当前正在进行的任务 8     List<Runnable> shutdownNow(); 9 10     // 线程池是否已关闭11     boolean isShutdown();12 13     // 如果调用了 shutdown() 或 shutdownNow() 方法后,所有任务结束了,那么返回true14     // 这个方法必须在调用shutdown或shutdownNow方法之后调用才会返回true15     boolean isTerminated();16 17     // 等待所有任务完成,并设置超时时间18     // 我们这么理解,实际应用中是,先调用 shutdown 或 shutdownNow,19     // 然后再调这个方法等待所有的线程真正地完成,返回值意味着有没有超时20     boolean awaitTermination(long timeout, TimeUnit unit)21             throws InterruptedException;22 23     // 提交一个 Callable 任务24     <T> Future<T> submit(Callable<T> task);25 26     // 提交一个 Runnable 任务,第二个参数将会放到 Future 中,作为返回值,27     // 因为 Runnable 的 run 方法本身并不返回任何东西28     <T> Future<T> submit(Runnable task, T result);29 30     // 提交一个 Runnable 任务31     Future<?> submit(Runnable task);32     33     ......34 }

复制代码

这些方法都很好理解,一个简单的线程池主要就是这些功能,能提交任务,能获取结果,能关闭线程池,这也是为什么我们经常用这个接口的原因。

回到顶部

AbstractExecutorService

 

AbstractExecutorService 抽象类派生自 ExecutorService 接口,然后在其基础上实现了几个实用的方法,这些方法提供给子类进行调用。

这个抽象类实现了 ExecutorService 中的 submit 方法,newTaskFor 方法用于将任务包装成 FutureTask。定义于最上层接口 Executor中的 void execute(Runnable command) 由于不需要获取结果,不会进行 FutureTask 的包装。

 

复制代码

 1 public abstract class AbstractExecutorService implements ExecutorService { 2  3     // RunnableFuture 是用于获取执行结果的,我们常用它的子类 FutureTask 4     // 下面两个 newTaskFor 方法用于将我们的任务包装成 FutureTask 提交到线程池中执行 5     protected <T> RunnableFuture<T> newTaskFor(Runnable runnable, T value) { 6         return new FutureTask<T>(runnable, value); 7     } 8  9     protected <T> RunnableFuture<T> newTaskFor(Callable<T> callable) {10         return new FutureTask<T>(callable);11     }12 13     // 提交任务14     public Future<?> submit(Runnable task) {15         if (task == null) throw new NullPointerException();16         // 1. 将任务包装成 FutureTask17         RunnableFuture<Void> ftask = newTaskFor(task, null);18         // 2. 交给执行器执行,execute 方法由具体的子类来实现19         // 前面也说了,FutureTask 间接实现了Runnable 接口。20         execute(ftask);21         return ftask;22     }23 24     public <T> Future<T> submit(Runnable task, T result) {25         if (task == null) throw new NullPointerException();26         // 1. 将任务包装成 FutureTask27         RunnableFuture<T> ftask = newTaskFor(task, result);28         // 2. 交给执行器执行29         execute(ftask);30         return ftask;31     }32 33     public <T> Future<T> submit(Callable<T> task) {34         if (task == null) throw new NullPointerException();35         // 1. 将任务包装成 FutureTask36         RunnableFuture<T> ftask = newTaskFor(task);37         // 2. 交给执行器执行38         execute(ftask);39         return ftask;40     }41 }

复制代码

到这里,我们发现,这个抽象类包装了一些基本的方法,可是 submit等方法,它们都没有真正开启线程来执行任务,它们都只是在方法内部调用了 execute 方法,所以最重要的 execute(Runnable runnable) 方法还没出现,这里我们要说的就是 ThreadPoolExecutor 类了。

回到顶部

ThreadPoolExecutor

我们经常会使用 Executors 这个工具类来快速构造一个线程池,对于初学者而言,这种工具类是很有用的,开发者不需要关注太多的细节,只要知道自己需要一个线程池,仅仅提供必需的参数就可以了,其他参数都采用作者提供的默认值。

复制代码

 1 public static ExecutorService newFixedThreadPool(int nThreads) { 2     return new ThreadPoolExecutor(nThreads, nThreads, 3                                   0L, TimeUnit.MILLISECONDS, 4                                   new LinkedBlockingQueue<Runnable>()); 5 } 6 public static ExecutorService newCachedThreadPool() { 7     return new ThreadPoolExecutor(0, Integer.MAX_VALUE, 8                                   60L, TimeUnit.SECONDS, 9                                   new SynchronousQueue<Runnable>());10 }

复制代码

这里先不说有什么区别,它们最终都会导向这个构造方法:

复制代码

 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     // 这几个参数都是必须要有的14     if (workQueue == null || threadFactory == null || handler == null)15         throw new NullPointerException();16 17     this.corePoolSize = corePoolSize;18     this.maximumPoolSize = maximumPoolSize;19     this.workQueue = workQueue;20     this.keepAliveTime = unit.toNanos(keepAliveTime);21     this.threadFactory = threadFactory;22     this.handler = handler;23 }

复制代码

上面的构造方法中列出了我们最需要关心的几个属性了,下面逐个介绍下构造方法中出现的这几个属性:

  • corePoolSize

    线程池中的核心线程数。

  • maximumPoolSize

    最大线程数,线程池允许创建的最大线程数。如果当前阻塞队列满了,且继续提交任务,则创建新的线程执行任务,前提是当前线程数小于maximumPoolSize;当阻塞队列是无界队列, 则maximumPoolSize则不起作用, 因为无法提交至核心线程池的线程会一直持续地放入workQueue

  • workQueue

    用来保存等待被执行的任务的阻塞队列. 在JDK中提供了如下阻塞队列:

    (1) ArrayBlockingQueue:基于数组结构的有界阻塞队列,按FIFO排序任务; 
    (2) LinkedBlockingQuene:基于链表结构的阻塞队列,按FIFO排序任务,吞吐量通常要高于ArrayBlockingQuene; 
    (3) SynchronousQuene:一个不存储元素的阻塞队列,每个插入操作必须等到另一个线程调用移除操作,否则插入操作一直处于阻塞状态,吞吐量通常要高于LinkedBlockingQuene; 
    (4) priorityBlockingQuene:具有优先级的无界阻塞队列;

    有兴趣的可以看看我前面关于BlockingQuene的文章

  • keepAliveTime

    空闲线程的保活时间,如果某线程的空闲时间超过这个值都没有任务给它做,那么可以被关闭了。注意这个值并不会对所有线程起作用,如果线程池中的线程数少于等于核心线程数 corePoolSize,那么这些线程不会因为空闲太长时间而被关闭,当然,也可以通过调用 allowCoreThreadTimeOut(true)使核心线程数内的线程也可以被回收;默认情况下,该参数只在线程数大于corePoolSize时才有用, 超过这个时间的空闲线程将被终止。

  • unit

    keepAliveTime的单位

  • threadFactory

    用于生成线程,一般我们可以用默认的就可以了。通常,我们可以通过它将我们的线程的名字设置得比较可读一些,如 Message-Thread-1, Message-Thread-2 类似这样。

  • handler

    线程池的饱和策略,当阻塞队列满了,且没有空闲的工作线程,如果继续提交任务,必须采取一种策略处理该任务,线程池提供了4种策略: 

      AbortPolicy:直接抛出异常,默认策略;
      CallerRunsPolicy:用调用者所在的线程来执行任务;
      DiscardOldestPolicy:丢弃阻塞队列中靠最前的任务,并执行当前任务;
      DiscardPolicy:直接丢弃任务; 
    当然也可以根据应用场景实现RejectedExecutionHandler接口,自定义饱和策略,如记录日志或持久化存储不能处理的任务。


除了上面几个属性外,我们再看看其他重要的属性。

复制代码

 1 private final AtomicInteger ctl = new AtomicInteger(ctlOf(RUNNING, 0)); 2  3 // 这里 COUNT_BITS 设置为 29(32-3),意味着前三位用于存放线程状态,后29位用于存放线程数 4 private static final int COUNT_BITS = Integer.SIZE - 3; 5  6 // 000 11111111111111111111111111111 7 // 这里得到的是 29 个 1,也就是说线程池的最大线程数是 2^29-1=536870911 8 // 以我们现在计算机的实际情况,这个数量还是够用的 9 private static final int CAPACITY   = (1 << COUNT_BITS) - 1;10 11 // 我们说了,线程池的状态存放在高 3 位中12 // 运算结果为 111跟29个0:111 0000000000000000000000000000013 private static final int RUNNING    = -1 << COUNT_BITS;14 // 000 0000000000000000000000000000015 private static final int SHUTDOWN   =  0 << COUNT_BITS;16 // 001 0000000000000000000000000000017 private static final int STOP       =  1 << COUNT_BITS;18 // 010 0000000000000000000000000000019 private static final int TIDYING    =  2 << COUNT_BITS;20 // 011 0000000000000000000000000000021 private static final int TERMINATED =  3 << COUNT_BITS;22 23 // 将整数 c 的低 29 位修改为 0,就得到了线程池的状态24 private static int runStateOf(int c)     { return c & ~CAPACITY; }25 // 将整数 c 的高 3 为修改为 0,就得到了线程池中的线程数26 private static int workerCountOf(int c)  { return c & CAPACITY; }27 28 private static int ctlOf(int rs, int wc) { return rs | wc; }29 30 private static boolean runStateLessThan(int c, int s) {31     return c < s;32 }33 34 private static boolean runStateAtLeast(int c, int s) {35     return c >= s;36 }37 38 private static boolean isRunning(int c) {39     return c < SHUTDOWN;40 }

复制代码

在这里,介绍下线程池中的各个状态和状态变化的转换过程:

  • RUNNING:这个没什么好说的,这是最正常的状态:接受新的任务,处理等待队列中的任务

  • SHUTDOWN:不接受新的任务提交,但是会继续处理等待队列中的任务

  • STOP:不接受新的任务提交,不再处理等待队列中的任务,中断正在执行任务的线程

  • TIDYING:所有的任务都销毁了,workCount 为 0。线程池的状态在转换为 TIDYING 状态时,会执行钩子方法 terminated()

  • TERMINATED:terminated() 方法结束后,线程池的状态就会变成这个

看了这几种状态的介绍,读者大体也可以猜到十之八九的状态转换了,各个状态的转换过程有以下几种:

  • RUNNING -> SHUTDOWN:当调用了 shutdown() 后,会发生这个状态转换,这也是最重要的

  • (RUNNING or SHUTDOWN) -> STOP:当调用 shutdownNow() 后,会发生这个状态转换,这下要清楚 shutDown() 和 shutDownNow() 的区别了

  • SHUTDOWN -> TIDYING:当任务队列和线程池都清空后,会由 SHUTDOWN 转换为 TIDYING

  • STOP -> TIDYING:当任务队列清空后,发生这个转换

  • TIDYING -> TERMINATED:这个前面说了,当 terminated() 方法结束后

另外,我们还要看看一个内部类 Worker,因为 Doug Lea 把线程池中的线程包装成了一个个 Worker,翻译成工人,就是线程池中做任务的线程。所以到这里,我们知道任务是 Runnable(内部叫 task 或 command),线程是 Worker。

 

复制代码

 1 private final class Worker 2     extends AbstractQueuedSynchronizer 3     implements Runnable{ 4     private static final long serialVersionUID = 6138294804551838833L; 5  6     // 这个是真正的线程,任务靠你啦 7     final Thread thread; 8  9     // 前面说了,这里的 Runnable 是任务。为什么叫 firstTask?因为在创建线程的时候,如果同时指定了10     // 这个线程起来以后需要执行的第一个任务,那么第一个任务就是存放在这里的(线程可不止执行这一个任务)11     // 当然了,也可以为 null,这样线程起来了,自己到任务队列(BlockingQueue)中取任务(getTask 方法)就行了12     Runnable firstTask;13 14     // 用于存放此线程完全的任务数,注意了,这里用了 volatile,保证可见性15     volatile long completedTasks;16 17     // Worker 只有这一个构造方法,传入 firstTask,也可以传 null18     Worker(Runnable firstTask) {19         setState(-1); // inhibit interrupts until runWorker20         this.firstTask = firstTask;21         // 调用 ThreadFactory 来创建一个新的线程,这里创建的线程到时候用来执行任务22         this.thread = getThreadFactory().newThread(this);23     }24 25     // 这里调用了外部类的 runWorker 方法26     public void run() {27         runWorker(this);28     }29 30     ...31 }

复制代码

有了上面的这些基础后,我们终于可以看看 ThreadPoolExecutor 的 execute 方法了,前面源码分析的时候也说了,各种方法都最终依赖于 execute 方法:

复制代码

 1 public void execute(Runnable command) { 2     if (command == null) 3         throw new NullPointerException(); 4  5     // 前面说的那个表示 "线程池状态" 和 "线程数" 的整数 6     int c = ctl.get(); 7  8     // 如果当前线程数少于核心线程数,那么直接添加一个 worker 来执行任务, 9     // 创建一个新的线程,并把当前任务 command 作为这个线程的第一个任务(firstTask)10     if (workerCountOf(c) < corePoolSize) {11         // 添加任务成功,那么就结束了。提交任务嘛,线程池已经接受了这个任务,这个方法也就可以返回了12         // 至于执行的结果,到时候会包装到 FutureTask 中。13         // 这里的true代表当前线程数小于corePoolSize,表示以corePoolSize为线程数界限14         if (addWorker(command, true))15             return;16         c = ctl.get();17     }18     // 到这里说明,要么当前线程数大于等于核心线程数,要么刚刚 addWorker 失败了19     // 如果线程池处于 RUNNING 状态,把这个任务添加到任务队列 workQueue 中20     if (isRunning(c) && workQueue.offer(command)) {21         int recheck = ctl.get();22         // 如果线程池已不处于 RUNNING 状态,那么移除已经入队的这个任务,并且执行拒绝策略23         if (! isRunning(recheck) && remove(command))24             reject(command);25         else if (workerCountOf(recheck) == 0)26             addWorker(null, false);27     }28     // 如果 workQueue 队列满了,那么进入到这个分支29     // 这里的false代表当前线程数大于corePoolSize,表示以 maximumPoolSize 为界创建新的 worker30     // 如果失败,说明当前线程数已经达到 maximumPoolSize,执行拒绝策略31     else if (!addWorker(command, false))32         reject(command);33 }

复制代码

 我们可以看看大体的执行流程

 

这个方法非常重要 addWorker(Runnable firstTask, boolean core) 方法,我们看看它是怎么创建新的线程的:

复制代码

 1 // 第一个参数是准备提交给这个线程执行的任务,之前说了,可以为 null 2 // 第二个参数为 true 代表使用核心线程数 corePoolSize 作为创建线程的界线,也就说创建这个线程的时候, 3 //         如果线程池中的线程总数已经达到 corePoolSize,那么返回false 4 //         如果是 false,代表使用最大线程数 maximumPoolSize 作为界线,线程池中的线程总数已经达到 maximumPoolSize,那么返回false 5 private boolean addWorker(Runnable firstTask, boolean core) { 6     retry: 7     for (;;) { 8         int c = ctl.get(); 9         int rs = runStateOf(c);10 11         // 如果线程池已关闭,并满足以下条件之一,那么不创建新的 worker:12         // 1. 线程池状态大于 SHUTDOWN,其实也就是 STOP, TIDYING, 或 TERMINATED13         // 2. firstTask != null14         // 3. workQueue.isEmpty()15         if (rs >= SHUTDOWN &&16             ! (rs == SHUTDOWN &&17                firstTask == null &&18                ! workQueue.isEmpty()))19             return false;20 21         for (;;) {22             int wc = workerCountOf(c);23             //这里就是通过core参数对当前线程数的判断24             if (wc >= CAPACITY ||25                 wc >= (core ? corePoolSize : maximumPoolSize))26                 return false;27             if (compareAndIncrementWorkerCount(c))28                 break retry;29             c = ctl.get();30             if (runStateOf(c) != rs)31                 continue retry;32             // else CAS failed due to workerCount change; retry inner loop33         }34     }35 36     /* 37      * 到这里,我们认为在当前这个时刻,可以开始创建线程来执行任务了,38      */39 40     // worker 是否已经启动41     boolean workerStarted = false;42     // 是否已将这个 worker 添加到 workers 这个 HashSet 中43     boolean workerAdded = false;44     Worker w = null;45     try {46         final ReentrantLock mainLock = this.mainLock;47         // 把 firstTask 传给 worker 的构造方法48         w = new Worker(firstTask);49         // 取 worker 中的线程对象,之前说了,Worker的构造方法会调用 ThreadFactory 来创建一个新的线程50         final Thread t = w.thread;51         if (t != null) {52             // 这个是整个类的全局锁,因为关闭一个线程池需要这个锁,至少我持有锁的期间,线程池不会被关闭53             mainLock.lock();54             try {55 56                 int c = ctl.get();57                 int rs = runStateOf(c);58 59                 // 小于 SHUTTDOWN 那就是 RUNNING60                 // 如果等于 SHUTDOWN,前面说了,不接受新的任务,但是会继续执行等待队列中的任务61                 if (rs < SHUTDOWN ||62                     (rs == SHUTDOWN && firstTask == null)) {63                     // worker 里面的 thread 可不能是已经启动的64                     if (t.isAlive())65                         throw new IllegalThreadStateException();66                     // 加到 workers 这个 HashSet 中67                     workers.add(w);68                     int s = workers.size();69                     // largestPoolSize 用于记录 workers 中的个数的最大值70                     // 因为 workers 是不断增加减少的,通过这个值可以知道线程池的大小曾经达到的最大值71                     if (s > largestPoolSize)72                         largestPoolSize = s;73                     workerAdded = true;74                 }75             } finally {76                 mainLock.unlock();77             }78             // 添加成功的话,启动这个线程79             if (workerAdded) {80                 // 启动线程,最重要的就是这里,下面我们会讲解如何执行任务81                 t.start();82                 workerStarted = true;83             }84         }85     } finally {86         // 如果线程没有启动,需要做一些清理工作,如前面 workCount 加了 1,将其减掉87         if (! workerStarted)88             addWorkerFailed(w);89     }90     // 返回线程是否启动成功91     return workerStarted;92 }

复制代码

上面第81行代码处已经启动了线程,w = new Worker(firstTask);  t = w.thread,我们接着看看Worker这个类

复制代码

 1 private final class Worker 2     extends AbstractQueuedSynchronizer 3     implements Runnable{ 4     private static final long serialVersionUID = 6138294804551838833L; 5     final Thread thread; 6     Runnable firstTask; 7     volatile long completedTasks; 8  9     // Worker 只有这一个构造方法,传入 firstTask10     Worker(Runnable firstTask) {11         setState(-1); // inhibit interrupts until runWorker12         this.firstTask = firstTask;13         // 调用 ThreadFactory 来创建一个新的线程,这里创建的线程到时候用来执行任务14         // 我们发现创建线程的时候传入的值是this,我们知道创建线程可以通过继承Runnable的方法,15         // Worker继承了Runnable,并且下面重写了run()方法16         this.thread = getThreadFactory().newThread(this);17     }18 19     // 由上面创建线程时传入的this,上面的thread启动后,会执行这里的run()方法,并且此时runWorker传入的也是this20     public void run() {21         runWorker(this);22     }23 }

复制代码

继续往下看 runWorker 方法:

复制代码

 1 // 此方法由 worker 线程启动后调用,这里用一个 while 循环来不断地从等待队列中获取任务并执行 2 // 前面说了,worker 在初始化的时候,可以指定 firstTask,那么第一个任务也就可以不需要从队列中获取 3 final void runWorker(Worker w) { 4     Thread wt = Thread.currentThread(); 5     // 该线程的第一个任务(如果有的话) 6     Runnable task = w.firstTask; 7     w.firstTask = null; 8     w.unlock(); // allow interrupts 9     boolean completedAbruptly = true;10     try {11         // 循环调用 getTask 获取任务12         while (task != null || (task = getTask()) != null) {13             w.lock();          
14             // 如果线程池状态大于等于 STOP,那么意味着该线程也要中断15             if ((runStateAtLeast(ctl.get(), STOP) ||16                  (Thread.interrupted() &&17                   runStateAtLeast(ctl.get(), STOP))) &&18                 !wt.isInterrupted())19                 wt.interrupt();20             try {21                 beforeExecute(wt, task);22                 Throwable thrown = null;23                 try {24                     // 到这里终于可以执行任务了,这里是最重要的,task是什么?是Worker 中的firstTask属性25                     // 也就是上面我们使用示例里面的 new MyRunnable()实例,这里就是真正的执行run方法里面的代码26                     task.run();27                 } catch (RuntimeException x) {28                     thrown = x; throw x;29                 } catch (Error x) {30                     thrown = x; throw x;31                 } catch (Throwable x) {32                     thrown = x; throw new Error(x);33                 } finally {34                     afterExecute(task, thrown);35                 }36             } finally {37                 // 一个任务执行完了,这个线程还可以复用,接着去队列中拉取任务执行38                 // 置空 task,准备 getTask 获取下一个任务39                 task = null;40                 // 累加完成的任务数41                 w.completedTasks++;42                 // 释放掉 worker 的独占锁43                 w.unlock();44             }45         }46         completedAbruptly = false;47     } finally {48         // 如果到这里,需要执行线程关闭:49         // 说明 getTask 返回 null,也就是超过corePoolSize的线程过了超时时间还没有获取到任务,也就是说,这个 worker 的使命结束了,执行关闭50         processWorkerExit(w, completedAbruptly);51     }52 }

复制代码

我们看看 getTask() 是怎么获取任务的

复制代码

 1 // 此方法有三种可能: 2 // 1. 阻塞直到获取到任务返回。我们知道,默认 corePoolSize 之内的线程是不会被回收的, 3 //      它们会一直等待任务 4 // 2. 超时退出。keepAliveTime 起作用的时候,也就是如果这么多时间内都没有任务,那么应该执行关闭 5 // 3. 如果发生了以下条件,此方法必须返回 null: 6 //    - 池中有大于 maximumPoolSize 个 workers 存在(通过调用 setMaximumPoolSize 进行设置) 7 //    - 线程池处于 SHUTDOWN,而且 workQueue 是空的,前面说了,这种不再接受新的任务 8 //    - 线程池处于 STOP,不仅不接受新的线程,连 workQueue 中的线程也不再执行 9 private Runnable getTask() {10     boolean timedOut = false; // Did the last poll() time out?11 12     retry:13     for (;;) {14         int c = ctl.get();15         int rs = runStateOf(c);16         // 两种可能17         // 1. rs == SHUTDOWN && workQueue.isEmpty()18         // 2. rs >= STOP19         if (rs >= SHUTDOWN && (rs >= STOP || workQueue.isEmpty())) {20             // CAS 操作,减少工作线程数21             decrementWorkerCount();22             return null;23         }24 25         boolean timed;      // Are workers subject to culling?26         for (;;) {27             int wc = workerCountOf(c);28             // 允许核心线程数内的线程回收,或当前线程数超过了核心线程数,那么有可能发生超时关闭29             timed = allowCoreThreadTimeOut || wc > corePoolSize;30             if (wc <= maximumPoolSize && ! (timedOut && timed))31                 break;32             if (compareAndDecrementWorkerCount(c))33                 return null;34             c = ctl.get();  // Re-read ctl35             // compareAndDecrementWorkerCount(c) 失败,线程池中的线程数发生了改变36             if (runStateOf(c) != rs)37                 continue retry;38             // else CAS failed due to workerCount change; retry inner loop39         }40         // wc <= maximumPoolSize 同时没有超时41         try {42             // 到 workQueue 中获取任务43             // 如果timed=wc > corePoolSize=false,我们知道核心线程数之内的线程永远不会销毁,则执行workQueue.take();我前面文章中讲过,take()方法是阻塞方法,如果队里中有任务则取到任务,如果没有任务,则一直阻塞在这里知道有任务被唤醒。44             //如果timed=wc > corePoolSize=true,这里将执行超时策略,poll(keepAliveTime, TimeUnit.NANOSECONDS)会阻塞keepAliveTime这么长时间,没超时就返回任务,超时则返回null.45             Runnable r = timed ?46                 workQueue.poll(keepAliveTime, TimeUnit.NANOSECONDS) :47                 workQueue.take();48             if (r != null)49                 return r;50             timedOut = true;51         } catch (InterruptedException retry) {52             // 如果此 worker 发生了中断,采取的方案是重试53             // 解释下为什么会发生中断,这个读者要去看 setMaximumPoolSize 方法,54             // 如果开发者将 maximumPoolSize 调小了,导致其小于当前的 workers 数量,55             // 那么意味着超出的部分线程要被关闭。重新进入 for 循环,自然会有部分线程会返回 null56             timedOut = false;57         }58     }59 }

复制代码

到这里,基本上也说完了整个流程,读者这个时候应该回到 execute(Runnable command) 方法,有两种情况会调用 reject(command) 来处理任务,因为按照正常的流程,线程池此时不能接受这个任务,所以需要执行我们的拒绝策略。接下来,我们说一说 ThreadPoolExecutor 中的拒绝策略。

1 final void reject(Runnable command) {2     // 执行拒绝策略3     handler.rejectedExecution(command, this);4 }

此处的 handler 我们需要在构造线程池的时候就传入这个参数,它是 RejectedExecutionHandler 的实例。

RejectedExecutionHandler 在 ThreadPoolExecutor 中有四个已经定义好的实现类可供我们直接使用,当然,我们也可以实现自己的策略,不过一般也没有必要。

复制代码

 1 // 只要线程池没有被关闭,那么由提交任务的线程自己来执行这个任务。 2 public static class CallerRunsPolicy implements RejectedExecutionHandler { 3     public CallerRunsPolicy() { } 4     public void rejectedExecution(Runnable r, ThreadPoolExecutor e) { 5         if (!e.isShutdown()) { 6             r.run(); 7         } 8     } 9 }10 11 // 不管怎样,直接抛出 RejectedExecutionException 异常12 // 这个是默认的策略,如果我们构造线程池的时候不传相应的 handler 的话,那就会指定使用这个13 public static class AbortPolicy implements RejectedExecutionHandler {14     public AbortPolicy() { }15     public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {16         throw new RejectedExecutionException("Task " + r.toString() +17                                              " rejected from " +18                                              e.toString());19     }20 }21 22 // 不做任何处理,直接忽略掉这个任务23 public static class DiscardPolicy implements RejectedExecutionHandler {24     public DiscardPolicy() { }25     public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {26     }27 }28 29 // 这个相对霸道一点,如果线程池没有被关闭的话,30 // 把队列队头的任务(也就是等待了最长时间的)直接扔掉,然后提交这个任务到等待队列中31 public static class DiscardOldestPolicy implements RejectedExecutionHandler {32     public DiscardOldestPolicy() { }33     public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {34         if (!e.isShutdown()) {35             e.getQueue().poll();36             e.execute(r);37         }38     }39 }

复制代码

到这里,ThreadPoolExecutor 算是分析得差不多了

回到顶部

推荐博客

  https://www.cnblogs.com/chen-haozi/p/10227797.html

回到顶部

总结

我们简单回顾下线程创建的流程

  1. 如果当前线程数少于 corePoolSize,那么提交任务的时候创建一个新的线程,并由这个线程执行这个任务;

  2. 如果当前线程数已经达到 corePoolSize,那么将提交的任务添加到队列中,等待线程池中的线程去队列中取任务;

  3. 如果队列已满,那么创建新的线程来执行任务,需要保证池中的线程数不会超过 maximumPoolSize,如果此时线程数超过了 maximumPoolSize,那么执行拒绝策略。

原文出处:https://www.cnblogs.com/java-chen-hao/p/10254260.html 

作者:chen_hao

打开App,阅读手记
0人推荐
发表评论
随时随地看视频慕课网APP