线
程协
程线程池
线程池介绍
线程的实现方式
线程池重点属性
ctl相关方法
线程池的具体实现
ThreadPoolExecutor
线程池的创建任务提交
参 数 解 释
线程池监控线程池原理
源码分析
execute方法
线程
线程是调度CPU资源的最小单位,线程模型分为KLT模型与ULT模型,JVM使用的KLT模型,Java线程与OS线程保持1:1的映射关系,也就是说有一个java线程也会在操作系统里有一个对应的线程。Java线程有多种生命状态
- NEW:新建
- RUNNABLE:运行
- BLOCKED:阻塞
- WAITING:等待
- TIMED_WAITING:超时等待
- TERMINATED: 终结
状态切换如下图所示:
协程
协程 (纤程,用户级线程),目的是为了追求最大力度的发挥硬件性能和提升软件的速度,协程基本原理是:在某个点挂起当前的任务,并且保存栈信息,去执行另一个任务;等完成或达到某个条件时,再还原原来的栈信息并继续执行(整个过程线程不需要上下文切换)。
Java原生不支持协程,在纯java代码里需要使用协程的话需要引入第三方包,如:quasar
线程池
“线程池”,顾名思义就是一个线程缓存,线程是稀缺资源,如果被无限制的创建,不仅会消耗系统资源,还会降低系统的稳定性,因此Java中提供线程池对线程进行统一分配、调优和监控
线程池介绍
在web开发中,服务器需要接受并处理请求,所以会为一个请求来分配一个线程来进行处理。如果每次请求都新创建一个线程的话实现起来非常简便,但是存在一个问题:
如果并发的请求数量非常多,但每个线程执行的时间很短,这样就会频繁的创建和销毁线程,如此一来会大大降低系统的效率。可能出现服务器在为每个请求创建新线程和销毁线程上花费的时间和消耗的系统资源要比处理实际的用户请求的时间和资源更多。
那么有没有一种办法使执行完一个任务,并不被销毁,而是可以继续执行其他的任务呢?
这就是线程池的目的了。线程池为线程生命周期的开销和资源不足问题提供了解决方案。通过对多个任务重用线程,线程创建的开销被分摊到了多个任务上。
什么时候使用线程池?
- 单个任务处理时间比较短需要处理的任务数量很大
线程池优势 - 重用存在的线程,减少线程创建,消亡的开销,提高性能
- 提高响应速度。当任务到达时,任务可以不需要的等到线程创建就能立即执行。
- 提高线程的可管理性。线程是稀缺资源,如果无限制的创建,不仅会消耗系统资源,还会降低系统的稳定性,使用线程池可以进行统一的分配,调优和监控。
线程的实现方式
Runnable,Thread,Callable
Executor框架
Executor接口是线程池框架中最基础的部分,定义了一个用于执行Runnable的execute方法。
下图为它的继承与实现
从图中可以看出Executor下有一个重要子接口ExecutorService,其中定义了线程池的具体行为
- execute(Runnable command):履行Ruannable类型的任务,
- submit(task):可用来提交Callable或Runnable任务,并返回代表此任务的Future 对象
- shutdown():在完成已提交的任务后封闭办事,不再接管新任务,
- shutdownNow():停止所有正在履行的任务并封闭办事。
- isTerminated():测试是否所有任务都履行完毕了。
- isShutdown():测试是否该ExecutorService已被关闭。
线程池重点属性
private final AtomicInteger ctl = new AtomicInteger(ctlOf(RUNNING, 0));
private static final int COUNT_BITS = Integer.SIZE 3;
private static final int CAPACITY = (1 << COUNT_BITS) 1;
ctl 是对线程池的运行状态和线程池中有效线程的数量进行控制的一个字段, 它包含两部分的信息: 线程池的运行状态 (runState) 和线程池内有效线程的数量 (workerCount),这里可以看到,使用了Integer类型来保存,高3位保存runState,低29位保存workerCount。COUNT_BITS 就是29,CAPACITY就是1左移29位减1(29个1),这个常量表示workerCount的上限值,大约是5亿。
ctl相关方法
private static int runStateOf(int c) { return c & ~CAPACITY; }
private static int workerCountOf(int c) { return c & CAPACITY; }
private static int ctlOf(int rs, int wc) { return rs | wc; }
- runStateOf:获取运行状态;
- workerCountOf:获取活动线程数;
- ctlOf:获取运行状态和活动线程数的值。
线程池存在5种状态
- RUNNING = ‐1 << COUNT_BITS; //高3位为111
- SHUTDOWN = 0 << COUNT_BITS; //高3位为000
- STOP = 1 << COUNT_BITS; //高3位为001
- TIDYING = 2 << COUNT_BITS; //高3位为010
- TERMINATED = 3 << COUNT_BITS ; //高3位为011
线程状态
-
RUNNING
(1) 状态说明:线程池处在RUNNING状态时,能够接收新任务,以及对已添加的任务进行处理。
(02) 状态切换:线程池的初始化状态是RUNNING。换句话说,线程池被一旦被创建,就处于RUNNING状态,并且线程池中的任务数为0! -
SHUTDOWN
(1)状态说明:线程池处在SHUTDOWN状态时,不接收新任务,但能处理已添加的任务。
(2)状态切换:调用线程池的shutdown()接口时,线程池由RUNNING -> SHUTDOWN。 -
STOP
(1)状态说明:线程池处在STOP状态时,不接收新任务,不处理已添加的任务,并且会中断正在处理的任务。
(2)状态切换:调用线程池的shutdownNow()接口时,线程池由(RUNNING or SHUTDOWN ) -> STOP。 -
TIDYING
(1)状态说明:当所有的任务已终止,ctl记录的”任务数量”为0,线程池会变为TIDYING 状态。当线程池变为TIDYING状态时,会执行钩子函数terminated()。terminated()在
ThreadPoolExecutor类中是空的,若用户想在线程池变为TIDYING时,进行相应的处理; 可以通过重载terminated()函数来实现。
(2)状态切换:当线程池在SHUTDOWN状态下,阻塞队列为空并且线程池中执行的任务也为空时,就会由 SHUTDOWN -> TIDYING。 当线程池在STOP状态下,线程池中执行的任务为空时,就会由STOP -> TIDYING。 -
TERMINATED
(1)状态说明:线程池彻底终止,就变成TERMINATED状态。
(2)状态切换:线程池处在TIDYING状态时,执行完terminated()之后,就会由 TIDYING - > TERMINATED。
- 进入TERMINATED的条件如下:
- 线程池不是RUNNING状态;
- 线程池状态不是TIDYING状态或TERMINATED状态;
- 如果线程池状态是SHUTDOWN并且workerQueue为空;
- workerCount为0;
- 设置TIDYING状态成功。
线程池的具体实现
- ThreadPoolExecutor 默认线程池
- ScheduledThreadPoolExecutor 定时线程池
ThreadPoolExecutor
线程池的创建
public ThreadPoolExecutor(int corePoolSize,int maximumPoolSize,long keepAliveTime,TimeUnit unit,BlockingQueue workQueue)
任务提交
public void execute() //提交任务无返回值
public Future<?> submit() //任务执行完成后有返回值
参数解释
corePoolSize
线程池中的核心线程数,当提交一个任务时,线程池创建一个新线程执行任务,直到当前线程数等于corePoolSize;如果当前线程数为corePoolSize,继续提交的任务被保存到 阻塞队列中,等待被执行;如果执行了线程池的prestartAllCoreThreads()方法,线程池会提前创建并启动所有核心线程。
maximumPoolSize
线程池中允许的最大线程数。如果当前阻塞队列满了,且继续提交任务,则创建新的线程执行任务,前提是当前线程数小于maximumPoolSize;
keepAliveTime
线程池维护线程所允许的空闲时间。当线程池中的线程数量大于corePoolSize的时
候,如果这时没有新的任务提交,核心线程外的线程不会立即销毁,而是会等待,直到等待的时间超过了keepAliveTime;
unit
keepAliveTime的单位;
workQueue
用来保存等待被执行的任务的阻塞队列,且任务必须实现Runable接口,在JDK中提供了如下阻塞队列:
- ArrayBlockingQueue:基于数组结构的有界阻塞队列,按FIFO排序任务;
- LinkedBlockingQuene:基于链表结构的阻塞队列,按FIFO排序任务,吞吐量通常要高于ArrayBlockingQuene;
- SynchronousQuene:一个不存储元素的阻塞队列,每个插入操作必须等到另一个线程调用移除操作,否则插入操作一直处于阻塞状态,吞吐量通常要高于LinkedBlockingQuene;
- priorityBlockingQuene:具有优先级的无界阻塞队列;
threadFactory
它是ThreadFactory类型的变量,用来创建新线程。默认使用Executors.defaultThreadFactory() 来创建线程。使用默认的ThreadFactory来创建线程时,会使新创建的线程具有相同的NORM_PRIORITY优先级并且是非守护线程,同时也设置了线程的名称。
handler
线程池的饱和策略,当阻塞队列满了,且没有空闲的工作线程,如果继续提交任务,必须采取一种策略处理该任务,线程池提供了4种策略:
- AbortPolicy:直接抛出异常,默认策略;
- CallerRunsPolicy:用调用者所在的线程来执行任务;
- DiscardOldestPolicy:丢弃阻塞队列中靠最前的任务,并执行当前任务;
- DiscardPolicy:直接丢弃任务;
上面的4种策略都是ThreadPoolExecutor的内部类。
当然也可以根据应用场景实现RejectedExecutionHandler接口,自定义饱和策略,如记录日志或持久化存储不能处理的任务。
线程池监控
public long getTaskCount() //线程池已执行与未执行的任务总数
public long getCompletedTaskCount() //已完成的任务数
public int getPoolSize() //线程池当前的线程数
public int getActiveCount() //线程池中正在执行任务的
线程池原理
先看构造方法。
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; }
接下来,看线程池最重要的方法execute()
private final AtomicInteger ctl = new AtomicInteger(ctlOf(RUNNING, 0)); // COUNT_BITS = 32 - 3 = 29 private static final int COUNT_BITS = Integer.SIZE - 3; // 1 << 29 = // 0010 0000 0000 0000 0000 0000 0000 0000 // 1 << 29 - 1 = // 0001 1111 1111 1111 1111 1111 1111 1111 private static final int CAPACITY = (1 << COUNT_BITS) - 1; // 1110 0000 0000 0000 0000 0000 0000 0000 private static final int RUNNING = -1 << COUNT_BITS; // 0000 0000 0000 0000 0000 0000 0000 0000 private static final int SHUTDOWN = 0 << COUNT_BITS; // 0010 0000 0000 0000 0000 0000 0000 0000 private static final int STOP = 1 << COUNT_BITS; // 0100 0000 0000 0000 0000 0000 0000 0000 private static final int TIDYING = 2 << COUNT_BITS; // 0110 0000 0000 0000 0000 0000 0000 0000 private static final int TERMINATED = 3 << COUNT_BITS; // ~CAPACITY = // 1110 0000 0000 0000 0000 0000 0000 0000 private static int runStateOf(int c) { return c & ~CAPACITY; } // CAPACITY = // 0001 1111 1111 1111 1111 1111 1111 1111 private static int workerCountOf(int c) { return c & CAPACITY; } private static int ctlOf(int rs, int wc) { return rs | wc; } private static boolean runStateLessThan(int c, int s) { return c < s; } private static boolean runStateAtLeast(int c, int s) { return c >= s; } private static boolean isRunning(int c) { return c < SHUTDOWN; } private boolean compareAndIncrementWorkerCount(int expect) { return ctl.compareAndSet(expect, expect + 1); } private boolean compareAndDecrementWorkerCount(int expect) { return ctl.compareAndSet(expect, expect - 1); } private void decrementWorkerCount() { do {} while (! compareAndDecrementWorkerCount(ctl.get())); } public void execute(Runnable command) { // 分 3 步进行: 1. 如果运行的线程少于 corePoolSize,尝试使用给定命令启动一个新线程作为其第一个任务。 // 对 addWorker 的调用以原子方式检查 runState 和 workerCount,因此通过返回 false 来防止在不应该添加线程时添加线程的 // 错误警报。 // 2. 如果一个任务可以成功加入队列,我们仍然需要仔细检查我们是否应该添加一个线程(因为现有的线程自上次检查后 // 死亡)或者池在进入此方法后关闭。因此,我们重新检查状态,如果有必要,如果停止入队,则回滚,或者如果没有, // 则启动一个新线程。 // 3.如果队列已满,我们尝试添加一个新线程。如果它失败了,说明线程池已经关闭或饱和,因此拒绝任务。 // 如果任务为空,则抛出空指针异常 if (command == null) throw new NullPointerException(); int c = ctl.get(); // 从这里可以看出,线程池最大线程数只有2 ^ 29 个 ,高三位用来表示线程池的状态 // 假如 c 的值为 // 1110 0000 0000 0000 0000 0000 0000 0011 // c & CAPACITY = // 1110 0000 0000 0000 0000 0000 0000 0011 & // 0001 1111 1111 1111 1111 1111 1111 1111 = // 0000 0000 0000 0000 0000 0000 0000 0011 // 因此得出线程池中工作线程数为 2^1 + 2 ^ 0 = 3 if (workerCountOf(c) < corePoolSize) { // 将当前Runnable 交给Worker来处理,如果交负成功,则直接返回 if (addWorker(command, true)) return; c = ctl.get(); } // 如果线程池肯定处于运行中状态,将当前Runnable加入到阻塞队列中 if (isRunning(c) && workQueue.offer(command)) { int recheck = ctl.get(); // 再次较验线程池状态,如果不是Running,则将之前加入的Runnable从队列中移除 if (! isRunning(recheck) && remove(command)) // 使用客户端定义的拒绝策略来处理Runnable reject(command); // 如果当前正在运行的Runnable个数为0,再创建一个新的Worker去消费队列中刚刚加入的Runnable // 什么情况下会出现所有的Worker都被销毁呢?当我们手动设置 es.allowCoreThreadTimeOut(true) 时 // 指定时间没有从队列中获取到任务,则Worker会从works中移除,如果刚好offer(command)方法调用之后,此时所有的worker // 都从线程池中移除,此时就需要新增加一个Worker来消费刚刚加入的Runnable else if (workerCountOf(recheck) == 0) addWorker(null, false); } // 如果队列已满,并且corePoolSize < maximumPoolSize时,会继续扩容Worker else if (!addWorker(command, false)) reject(command); }
将当前Runnable加入到工作线程中
private boolean addWorker(Runnable firstTask, boolean core) { retry: for (;;) { int c = ctl.get(); // 获取当前线程池状态 // 如果当前线程池c的值为 // 1110 0000 0000 0000 0000 0000 0000 0011 // 则 c & ~CAPACITY = // 1110 0000 0000 0000 0000 0000 0000 0011 & // 1110 0000 0000 0000 0000 0000 0000 0000 = // 1110 0000 0000 0000 0000 0000 0000 0000 // 因此rs = 111 ,也就是运行中状态 int rs = runStateOf(c); // 如果当前线程池的state 是STOP,TIDYING,TERMINATED,SHUTDOWN 四种状态中的一种 // 并且 (线程池状态不是SHUTDOWN || firstTask !=null || workQueue.isEmpty() ) // 直接返回false。因此得出结论 // 1.凡是state 是STOP,TIDYING,TERMINATED,则不允许添加新的Worker来处理 // 2.如果state是SHUTDOWN并且workQueue.isNotEmpty() 并且,firstTask==null ,则是可以添加新的Worker来处理workQueue的任务的 // 3.如果state是SHUTDOWN并且workQueue.isNotEmpty() ,但firstTask !=null,是不允许添加新的Worker来处理firstTask任务的 // 4.如果state是SHUTDOWN并且workQueue.isEmpty(),但firstTask == null,也不允许添加新的Worker到线程池中 if (rs >= SHUTDOWN && ! (rs == SHUTDOWN && firstTask == null && ! workQueue.isEmpty())) return false; for (;;) { // 如果当前Worker 数大于 2 ^ 29 或 // 如果core = true ,表示阻塞队列没有满,如果当前Worker数大于等于corePoolSize,则用拒绝策略来处理Runnable // 如果core = false,表示阻塞队列已满,如果此时WorKer数大于等于 maximumPoolSize,则也用拒绝策略来处理Runnable int wc = workerCountOf(c); if (wc >= CAPACITY || wc >= (core ? corePoolSize : maximumPoolSize)) return false; // CAS 增加当前Worker数 if (compareAndIncrementWorkerCount(c)) // 如果CAS操作成功,则goto到retry位置,不再进入for循环,继续后面的代码 break retry; c = ctl.get(); // 如果CAS增加当前Worker数失败,并且线程池的状态已经改变 // 则跳转到retry ,重新进入for循环,如果线程池的状态没有改变,则只是重新进入当前循环,继续CAS 增加Worker数 if (runStateOf(c) != rs) continue retry; } } boolean workerStarted = false; boolean workerAdded = false; Worker w = null; try { final ReentrantLock mainLock = this.mainLock; // 创建一个新的Worker w = new Worker(firstTask); final Thread t = w.thread; if (t != null) { // 用ReentrantLock加锁 mainLock.lock(); try { int c = ctl.get(); int rs = runStateOf(c); // 如果当前线程池的状态是RUNNING或 // 当前线程池状态为 SHUTDOWN 但 firstTask == null ,什么时候会出现这种情况呢? // 当线程池状态为SHUTDOWN,但workQueue不为空,此时添加一个Worker来加快workQueue中的任务处理 if (rs < SHUTDOWN || (rs == SHUTDOWN && firstTask == null)) { // isAlive测试此线程是否存在。如果线程已启动且尚未死亡,则该线程处于活动状态。 // 如果线程处于活动状态,但这个Workder刚创建,还并没有start(),因此将抛出IllegalThreadStateException异常 if (t.isAlive()) throw new IllegalThreadStateException(); // 将新创建的Worker加入到线程池中 workers.add(w); int s = workers.size(); // 保存曾经workers达到的最大数 if (s > largestPoolSize) largestPoolSize = s; workerAdded = true; } } finally { // 用ReentrantLock解锁 mainLock.unlock(); } // 如果Worker创建并添加成功 if (workerAdded) { // 启动线程,实际上是运行Worker中的run方法 t.start(); workerStarted = true; } } } finally { // 如果Worker启动失败 if (! workerStarted) addWorkerFailed(w); } return workerStarted; }
在addWorker()中有一个基本知识,可能大家有点迷惑,就是break retry; 和 continue retry;的区别。请看下面例子,我相信你肯定会明白。
public class Test { public static void main(String[] args) { breakRetry(); System.out.println("========================"); continueRetry(); } private static void breakRetry() { int i = 0; retry: for (; ; ) { System.out.println("start"); for (; ; ) { i++; if (i == 4) break retry; } } //start 进入外层循环 //4 System.out.println(i); } private static void continueRetry() { int i = 0; retry: for (; ; ) { System.out.println("start"); for (; ; ) { i++; if (i == 3) continue retry; System.out.println("end = " + i); if (i == 4) break retry; } } //start 第一次进入外层循环 //end i=1输出 //end i=2输出 //start 再次进入外层循环 //end i=4输出 //4 最后输出 System.out.println(i); } }
我们接着看,如果Worker添加失败了,线程池会怎样处理呢?
private void addWorkerFailed(Worker w) { final ReentrantLock mainLock = this.mainLock; mainLock.lock(); try { // 如果Worker不为空,则从workers中移除 if (w != null) workers.remove(w); // CAS 减少线程池的workers 的计数值 decrementWorkerCount(); // 偿试终止线程池 tryTerminate(); } finally { mainLock.unlock(); } } private void decrementWorkerCount() { do {} while (! compareAndDecrementWorkerCount(ctl.get())); } final void tryTerminate() { for (;;) { int c = ctl.get(); // 如果当前线程池状态是RUNNING 或 // 当前线程池状态是TIDYING或TERMINATED 或 // 当前线程池状态是SHUTDOWN 但 workQueue不为空,则什么都不做 if (isRunning(c) || runStateAtLeast(c, TIDYING) || (runStateOf(c) == SHUTDOWN && ! workQueue.isEmpty())) return; // 如果线程池状态为STOP 或 // 当前线程池状态为SHUTDOWN 但 workQueue为空,则从wokers中,中断其中一个Worker的Thread if (workerCountOf(c) != 0) { interruptIdleWorkers(ONLY_ONE); return; } // 线程池状态为STOP 或 当前线程池状态为SHUTDOWN 但 workQueue为空 // 并且线程池中woker数为0 ,则将当前线程池的状态设置为TERMINATED 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)); // 唤醒条件队列中所有的节点 termination.signalAll(); } return; } } finally { mainLock.unlock(); } } } private void interruptIdleWorkers(boolean onlyOne) { final ReentrantLock mainLock = this.mainLock; mainLock.lock(); try { for (Worker w : workers) { Thread t = w.thread; // 如果Worker中线程没有被中断,并且当前线程并没有在调用task的run()方法 if (!t.isInterrupted() && w.tryLock()) { try { // Worker的线程设置中断标志位 t.interrupt(); } catch (SecurityException ignore) { } finally { w.unlock(); } } // 如果onlyOne为true,则只设置其中一个Worker的线程中断标识位,即退出 if (onlyOne) break; } } finally { mainLock.unlock(); } }
上面的分析,线程池的整体架构已经初步成型,但在addWorker()方法中,只看到new 了一个Woker,并且启动了Woker中的线程t,就没有看到其他的东西了。那深入Worker来看具体实现。
private final class Worker extends AbstractQueuedSynchronizer implements Runnable { final Thread thread; Runnable firstTask; volatile long completedTasks; Worker(Runnable firstTask) { // 初始化state的状态为-1 setState(-1); this.firstTask = firstTask; this.thread = getThreadFactory().newThread(this); } public void run() { runWorker(this); } protected boolean isHeldExclusively() { return getState() != 0; } 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; } public void lock() { acquire(1); } public boolean tryLock() { return tryAcquire(1); } public void unlock() { release(1); } public boolean isLocked() { return isHeldExclusively(); } void interruptIfStarted() { Thread t; if (getState() >= 0 && (t = thread) != null && !t.isInterrupted()) { try { t.interrupt(); } catch (SecurityException ignore) { } } } }
先来看Worker构造函数,我们着重看 getThreadFactory().newThread(this); 默认情况下, threadFactory是DefaultThreadFactory,我们进入DefaultThreadFactory看其具体实现。
static class DefaultThreadFactory implements ThreadFactory { private static final AtomicInteger poolNumber = new AtomicInteger(1); private final ThreadGroup group; private final AtomicInteger threadNumber = new AtomicInteger(1); private final String namePrefix; DefaultThreadFactory() { SecurityManager s = System.getSecurityManager(); group = (s != null) ? s.getThreadGroup() : Thread.currentThread().getThreadGroup(); namePrefix = "pool-" + poolNumber.getAndIncrement() + "-thread-"; } public Thread newThread(Runnable r) { // 所有的wokers的Thread都属于一个线程组,r 为当前Worker,Worker实现了Runnable接口 Thread t = new Thread(group, r, namePrefix + threadNumber.getAndIncrement(), 0); // 非守护进程 if (t.isDaemon()) t.setDaemon(false); // 线程的优先级默认为5 if (t.getPriority() != Thread.NORM_PRIORITY) t.setPriority(Thread.NORM_PRIORITY); return t; } }
看到DefaultThreadFactory的实现后,我们立即明白,Worker的Thread.start()实际上是调用了Worker的run()方法,因为在new Thread时,传入了r 就是Worker,接下来,我们进入Worker的run()方法。看其具体实现。
public void run() { runWorker(this); } final void runWorker(Worker w) { Thread wt = Thread.currentThread(); Runnable task = w.firstTask; // 如果firstTask不为空,while()循环第一次运行firstTask的Runnable,后面则从getTask() // 中取任务来运行 w.firstTask = null; // 这里的w.unlock()并不是 mainLock.unlock(),我之前也以为unlock是ReentrantLock中的方法 // 看半天都不明白,后面发现 Worker本身也实现了AbstractQueuedSynchronizer接口,这里的w.unlock() // 实际上调用了Worker中的unlock()方法,细心的读者会发现在Worker构造函数中setState(-1);将state设置为1 // 而这里的unlock()方法将state设置为0,方便后面的w.lock()方法调用 ,为什么Worker不一开始就将state赋值为0呢? // 在setState(-1);方法后面有一行注释【禁止中断直到 runWorker 】这个什么意思呢?假如t.start()刚启动 // 立即手动调用shutdownNow()方法,而shutdownNow()方法最终会调用interruptWorkers()方法, // 在interruptWorkers()方法中,会调用每个Worker的interruptIfStarted()方法, // interruptIfStarted()方法中有一个判断必须getState() >= 0才会调用t.interrupt(); 即使调用了 // shutdownNow()方法,也只对运行到runWorker()方法中的w.unlock()这行代码之后的线程生效。对没有运行到runWorker() // 方法的线程不生效 w.unlock(); boolean completedAbruptly = true; try { while (task != null || (task = getTask()) != null) { // 获取锁,将state设置为1 w.lock(); // 下面这几行代码的意思是,只要线程池的状态为STOP,TIDYING,或TERMINATED 总会中断当前线程 // 下面分为两种情况 // 1. 调用了shutdownNow(),但此时线程并没有运行到runWorker()方法,此时线程池的状态被设置为STOP,但线程并未被设置 // 中断标志位,因此需要调用wt.interrupt();为线程设置中断标识位 // 2. 调用了shutdownNow()方法,此时线程运行到了runWorker()方法内部,此时线程池的状态被设置为STOP, // 线程被设置了中断标志位,但此时调用了Thread.interrupted() 方法方法清除了 // 中断标识位,因此仍然需要调用 wt.interrupt();恢复中断标志位 // 中断标志位设置的意义是什么呢? 如果我们想,只要调用了线程池shutdownNow()方法,就立刻停止所有线程运行 // 我们可以在Runnable的run()方法中实现,如果Thread.interrupted()==true,则立即return掉,不再做业务处理 // 这样就达到了立即停止所有的线程运行的效果,如果在Runnable的run()方法不做任何处理 // 即使调用了线程池的shutdownNow()方法处理,也不会立即停止,必须当前Runnable的run()方法运行完 // 在获取下一个任务时,才会彻底的停掉当前Worker if ((runStateAtLeast(ctl.get(), STOP) || // 如果线程设置了中断标志位,Thread.interrupted()会清除中断标志位,并返回true (Thread.interrupted() && runStateAtLeast(ctl.get(), STOP))) && !wt.isInterrupted()) // 设置线程中断标识位 wt.interrupt(); try { // 在ThreadPoolExecutor中,beforeExecute()和afterExecute()方法是空实现 // 便于子类去实现相应逻辑 beforeExecute(wt, task); Throwable thrown = null; try { // 调用Runnable的run()方法,这里只是普通方法调用 task.run(); } catch (RuntimeException x) { // 将RuntimeException异常记录在thrown中 thrown = x; throw x; } catch (Error x) { // 将Error异常记录在thrown中 thrown = x; throw x; } catch (Throwable x) { // 将Throwable异常记录在thrown中 thrown = x; throw new Error(x); } finally { // 子类可以实现afterExecute()方法,根据异常信息做相应的处理 afterExecute(task, thrown); } } finally { task = null; // 当前线程完成的任务增加1 w.completedTasks++; // 解锁 w.unlock(); } } // 退出while()循环,回收线程 completedAbruptly = false; } finally { // 处理回收线程 processWorkerExit(w, completedAbruptly); } }
接下来,我们看看getTask()方法的内部实现。
private Runnable getTask() { boolean timedOut = false; retry: for (;;) { int c = ctl.get(); int rs = runStateOf(c); // 如果当前线程池状态为SHUTDOWN,并且workQueue为空,则将Worker从workers中移除 // 如果当前线程池状态为STOP,或TIDYING,或TERMINATED ,则workQueue中有没有任务,当前Worker立即停止掉 if (rs >= SHUTDOWN && (rs >= STOP || workQueue.isEmpty())) { // 减少线程池的WorKer数 decrementWorkerCount(); return null; } boolean timed; for (;;) { int wc = workerCountOf(c); // 下面逻辑分两种情况 // 1. 如果手动设置了allowCoreThreadTimeOut =true // 只要Worker等待了keepAliveTime 纳秒没有从队列中获得任务 ,当前Worker将被清除掉 // 2. 只要当前线程池的Worker数量大于corePoolSize核心线程数,则等待keepAliveTime纳秒后,没有从workQueue中获得任务 // 当前Worker将被回收掉 // 从上面的分析中我们得出了结论,只要设置了allowCoreThreadTimeOut =true ,任何Worker在等待keepAliveTime后,没有从workQueue // 中领取到任务 ,Worker将被回收掉。如果一直没有任务加入到workQueue,所以的Worker都有回收掉的可能 // 如果没有设置allowCoreThreadTimeOut =true ,线程池只会回收掉maximumPoolSize - corePoolSize的Worker // corePoolSize数量的Worker将永柱线程池, 一直take()等待任务的到来 timed = allowCoreThreadTimeOut || wc > corePoolSize; if (wc <= maximumPoolSize && ! (timedOut && timed)) break; // 如果满足上面分析的任何一个条件,Worker将被回收掉,Worker将从workers中移除 if (compareAndDecrementWorkerCount(c)) return null; c = ctl.get(); // 当然在调用compareAndDecrementWorkerCount()方法过程中会存在并发情况 ,如果操作失败 // 当前循环重新进行 if (runStateOf(c) != rs) // 如果在这个过程中,线程池的状态发生改变,则需要跳转到retry中,重新进入循环 // 重新对线程池的状态进行判断,如之前线程池的状态是Running,此时调用了shutdownNow()方法 // 线程池状态由Running变为STOP状态,则直接退出getTask()方法,并返回null,当前Worker直接回收掉 continue retry; } try { // 如果手动设置了allowCoreThreadTimeOut =true,或当前workers的数量大于 corePoolSize // 则调用 workQueue.poll(keepAliveTime, TimeUnit.NANOSECONDS) 方法 // 如果依然没有从队列中获取到任务,当前Worker将被回收掉,如果在take或pull()过程中抛出了中断异常 // 则继续等待从队列中获取任务 Runnable r = timed ? workQueue.poll(keepAliveTime, TimeUnit.NANOSECONDS) : workQueue.take(); if (r != null) return r; timedOut = true; } catch (InterruptedException retry) { timedOut = false; } } }
我相信经过上面分析,你对getTask()方法已经有了深入理解,只要getTask()返回null,则Worker就会被回收掉,而导致getTask()返回空有3种情况 。
- 线程池的状态发生改变,如调用了shutdowNow()方法,或shutdown()方法 。
- 我们手动设置了allowCoreThreadTimeOut为true,并且线程在等待了keepAliveTime纳秒后,依然没有从队列中获取到任务
- 当Worker数量大于corePoolSize,并且Worker在等待keepAliveTime纳秒后,依然没有从队列中获取到任务 ,当前Worker将被回收掉。
接下来看,Worker是如何回收的。
private void processWorkerExit(Worker w, boolean completedAbruptly) { // 如果是调用getTask()返回 null而退出runWorker方法中的while()循环,则此时workers数已经减少1了 // 如果因为其他情况抛出了异常,而退出while()循环的,此时completedAbruptly=true ,还需要将wokers数减1 if (completedAbruptly) decrementWorkerCount(); final ReentrantLock mainLock = this.mainLock; // 在修改completedTaskCount和workers移除时,可能存在并发情况,需要获取lock()锁 mainLock.lock(); try { completedTaskCount += w.completedTasks; // 将w从workers中移除 workers.remove(w); } finally { mainLock.unlock(); } // 尝试将线程池的状态设置为TERMINATED tryTerminate(); int c = ctl.get(); // 如果当前线程池状态为RUNNING 或 当前线程池状态是SHUTDOWN 但 workQueue不为空 if (runStateLessThan(c, STOP)) { // 如果是getTask()方法返回null,Worker正常终止的 if (!completedAbruptly) { // 如果手动设置了allowCoreThreadTimeOut为true // 则至少保证Worker数要大于 1 即可 // 如果没有设置 allowCoreThreadTimeOut为true,则也需要保证Worker数要大于等于corePoolSize // 如果小于min,则添加一个Worker ,继续调用getTask()方法,从队列中获取任务来处理 int min = allowCoreThreadTimeOut ? 0 : corePoolSize; if (min == 0 && ! workQueue.isEmpty()) min = 1; if (workerCountOf(c) >= min) return; // replacement not needed } // 向线程池中添加一个新的Worker ,什么情况下会出现需要向Worker中addWorker呢?也分为两种情况 // 1. 如果设置allowCoreThreadTimeOut=true,则所有的Worker等待keepAliveTime生,仍然没有从队列中 // poll()到任务 ,所有的Worker的getTask()方法将返回null,并且Worker数为0 ,但此时有新的任务被添加到workQueue队列中 // 则需要创建一个新的Worker来消费队列中的任务 // 2. 如果没有设置allowCoreThreadTimeOut=true为true ,在execute()方法中 // if (isRunning(c) && workQueue.offer(command)) ,蓝色节点执行,此时调用了shutdown()方法 // 线程池状态由RUNNING变为了SHUTDOWN,并且此时workQueue为空,此时getTask()方法中这行代码执行 // if (rs >= SHUTDOWN && (rs >= STOP || workQueue.isEmpty())) ,并返回true // 有Worker都将调用decrementWorkerCount()方法并返回null,当这个Worker执行 // if (min == 0 && ! workQueue.isEmpty()) 到蓝色代码之前,此时 execute()方法继续执行 // if (isRunning(c)&& workQueue.offer(command)) ,任务被加入到队列中, // 因此会出现线程池状态为SHUTDOWN,但workQueue不为空,而当前Worker数少于corePoolSize的情况 // 因此得出结论,即使设置了allowCoreThreadTimeOut为true,当有新的任务加入时,当Worker为0时,也会添加Worker来消费workQueue中的任务 // 当allowCoreThreadTimeOut为false,即使当前线程池状态为SHUTDOWN,只要workQueue中有任务 ,也要努力将Worker数达到corePoolSize // 来消费workQueue中的任务 addWorker(null, false); } }
在processWorkerExit()方法中的逻辑还是好理解的,无非是将Worker从workers中移除,同时只要当前workQueue不为空,并且线程池状态为RUNNING或SHUTDOWN,总要保证一定数量的Worker来消费workQueue中的任务 。
我们理解了线程池的核心原理,再来看关于线程池api方法 。
shutdown
public void shutdown() { final ReentrantLock mainLock = this.mainLock; mainLock.lock(); try { // 获得线程可修改权限 checkShutdownAccess(); // 将当前线程池的状态设置为SHUTDOWN advanceRunState(SHUTDOWN); // 中断所有的Worker interruptIdleWorkers(); // 钩子函数,提供给子类实现 onShutdown(); // hook for ScheduledThreadPoolExecutor } finally { mainLock.unlock(); } // 偿试将线程池的状态设置为TERMINATED,但不一定成功 tryTerminate(); } private void checkShutdownAccess() { SecurityManager security = System.getSecurityManager(); if (security != null) { security.checkPermission(shutdownPerm); final ReentrantLock mainLock = this.mainLock; mainLock.lock(); try { for (Worker w : workers) //运行时关闭许可 security.checkAccess(w.thread); } finally { mainLock.unlock(); } } } private void advanceRunState(int targetState) { for (;;) { int c = ctl.get(); // 如果targetState为SHUTDOWN,则当前线程池状态为SHUTDOWN,STOP,TIDYING,TERMINATED // 则直接返回,如果当前线程池状态为RUNNING,则将当前线程池状态直到设置为SHUTDOWN为止 if (runStateAtLeast(c, targetState) || ctl.compareAndSet(c, ctlOf(targetState, workerCountOf(c)))) break; } }
shutdownNow
public List<Runnable> shutdownNow() { List<Runnable> tasks; final ReentrantLock mainLock = this.mainLock; mainLock.lock(); try { // 获得线程可修改权限 checkShutdownAccess(); // 将线程状态设置为STOP advanceRunState(STOP); // 中断所有能中断的Worker中的线程 interruptWorkers(); // 将未消费的任务添加到tasks中,并将任务从workQueue移除 tasks = drainQueue(); } finally { mainLock.unlock(); } // 偿试将线程状态设置为TERMINATED tryTerminate(); return tasks; }
drainQueue方法,将未消费的任务添加到List中并返回。接下来,我们来看drainQueue()方法的实现。
private List<Runnable> drainQueue() { BlockingQueue<Runnable> q = workQueue; List<Runnable> taskList = new ArrayList<Runnable>(); // 调用线程池的drainTo方法,将queue中的任务转移到taskList中 q.drainTo(taskList); // 当queue是DelayQueue,此时通过移除头节点的方式遍历整个队列不可行,需要 // 调用toArray方法来实现将队列中的任务转移到tasks中 if (!q.isEmpty()) { for (Runnable r : q.toArray(new Runnable[0])) { if (q.remove(r)) taskList.add(r); } } return taskList; }
我们之前在ArrayBlockingQueue&LinkedBlockingQueue&DelayQueue&SynchronousQueue&PriorityBlockingQueue源码解析博客中,对于每个队列的drainTo()方法,并没有分析过,现在用到了,我们接着分析 。我们选两种队列来分析 ,LinkedBlockingQueue和DelayQueue,先来看LinkedBlockingQueue的drainTo方法。
public int drainTo(Collection<? super E> c) { return drainTo(c, Integer.MAX_VALUE); } public int drainTo(Collection<? super E> c, int maxElements) { if (c == null) throw new NullPointerException(); if (c == this) throw new IllegalArgumentException(); final ReentrantLock lock = this.lock; lock.lock(); try { int n = Math.min(maxElements, count); for (int i = 0; i < n; i++) { c.add(first.item); // In this order, in case add() throws. // 将头节点移除 unlinkFirst(); } return n; } finally { lock.unlock(); } } private E unlinkFirst() { Node<E> f = first; if (f == null) return null; Node<E> n = f.next; E item = f.item; f.item = null; f.next = f; first = n; if (n == null) last = null; else n.prev = null; --count; notFull.signal(); return item; }
在drainTo()方法中,将头节点的item加入到List中,并且将头节点从队列中移除,以此类推,直到整个队列为空。
我们先看DelayQueue的drainTo方法。
public int drainTo(Collection<? super E> c) { if (c == null) throw new NullPointerException(); if (c == this) throw new IllegalArgumentException(); final ReentrantLock lock = this.lock; lock.lock(); try { int n = 0; for (;;) { E first = q.peek(); if (first == null || first.getDelay(TimeUnit.NANOSECONDS) > 0) break; c.add(q.poll()); ++n; } return n; } finally { lock.unlock(); } }
延迟队列从头节点移除元素,如果时间没有达到要求,此时是不能从头节点中poll()到元素的。请看加粗代码,因此对于延迟队列这类workQueue,只能通过toArray方法来将队列中未消费任务转移到tasks中,接下来,我们来看DelayQueue的toArray方法。
public <T> T[] toArray(T[] a) { final ReentrantLock lock = this.lock; lock.lock(); try { return q.toArray(a); } finally { lock.unlock(); } } public <T> T[] toArray(T[] a) { if (a.length < size) // 将队列中值拷贝到数组中 return (T[]) Arrays.copyOf(queue, size, a.getClass()); System.arraycopy(queue, 0, a, 0, size); if (a.length > size) a[size] = null; return a; }
现在你应该明白 drainQueue()方法中分两种情况来处理队列中的元素了吧。
isTerminating&isTerminated
// 线程池状态是否正在由SHUTDOWN向TERMINATED变化 public boolean isTerminating() { int c = ctl.get(); return ! isRunning(c) && runStateLessThan(c, TERMINATED); } //线程池状态是否是TERMINATED public boolean isTerminated() { return runStateAtLeast(ctl.get(), TERMINATED); }
awaitTermination
public boolean awaitTermination(long timeout, TimeUnit unit) throws InterruptedException { // 计算wait()的纳秒 long nanos = unit.toNanos(timeout); final ReentrantLock mainLock = this.mainLock; mainLock.lock(); try { for (;;) { // 线程池的状态是否是TERMINATED,如果是,则返回true if (runStateAtLeast(ctl.get(), TERMINATED)) return true; if (nanos <= 0) return false; nanos = termination.awaitNanos(nanos); } } finally { mainLock.unlock(); } }
在等待timeout的时间之内,线程池的状态是否变成TERMINATED。
setCorePoolSize
public void setCorePoolSize(int corePoolSize) { // 如果corePoolSize小于0,则抛出IllegalArgumentException异常 if (corePoolSize < 0) throw new IllegalArgumentException(); // 计算调整之后的核心线程池数量和当前线程池核心线程数量的差值 int delta = corePoolSize - this.corePoolSize; this.corePoolSize = corePoolSize; // // 当delta少于0,则当前设置的核心线程数要小于之前线程池的核心线程数,这里分为两种情况 // 1.如果当前线程池的核心线程池数大于设置的核心线程数,此时需要为所有Worker的线程设置中断标识 // 使得park()的线程从阻塞中唤醒,从而减少核心线程数 // 2.如果当前线程池中核心线程数量小于设置的核心线程数,这时只需要平稳过度即可,不需要做任何操作 if (workerCountOf(ctl.get()) > corePoolSize) interruptIdleWorkers(); // 如果当前设置的核心线程数量大于当前线程池的数量 else if (delta > 0) { // 这里用到了一个算法,计算本次要扩容的Worker数和之前的Worker数,取小的值作为本次要扩容的数量 // 举下例子,之前线程池核心线程数 Worker数为2,此时设置新的核心线程数为10,delta= 10 - 2 = 8 // min (8,2) = 2 ,此时最多将核心线程数扩容为4,如果还需要扩容Worker数,只能通过调用execute()方法, // 通过addWorker()方法进行扩容了,当然啦,这也是作者为了性能考虑,在setCorePoolSize()最多将线程数量扩容为 // 原来的两倍,如果workQueue为空,最多扩容一个Worker就停止扩容了,如果队列为空,即使扩容Worker也是 // 无尽的等待,与其浪费性能,不如先不扩容Worker数 int k = Math.min(delta, workQueue.size()); while (k-- > 0 && addWorker(null, true)) { // 如果任务队列为空,则停止扩容Worker if (workQueue.isEmpty()) break; } } }
我们再来看 interruptIdleWorkers()方法,为什么调用interruptIdleWorkers()方法,就能实现核心线程数缩容了呢?在interruptIdleWorkers()方法中,只是将Worker中的线程,调用其 t.interrupt();方法,设置中断标识,如何就达到了线程的缩容?我们再次回头来看 getTask()和runWorker()方法。首先看getTask()方法。
private Runnable getTask() { boolean timedOut = false; // Did the last poll() time out? retry: for (;;) { int c = ctl.get(); int rs = runStateOf(c); if (rs >= SHUTDOWN && (rs >= STOP || workQueue.isEmpty())) { decrementWorkerCount(); return null; } boolean timed; for (;;) { int wc = workerCountOf(c); timed = allowCoreThreadTimeOut || wc > corePoolSize; if (wc <= maximumPoolSize && ! (timedOut && timed)) break; if (compareAndDecrementWorkerCount(c)) return null; c = ctl.get(); if (runStateOf(c) != rs) continue retry; } try { Runnable r = timed ? workQueue.poll(keepAliveTime, TimeUnit.NANOSECONDS) : workQueue.take(); if (r != null) return r; timedOut = true; } catch (InterruptedException retry) { timedOut = false; } } }
在Worker缩容时,调用Worker中线程的t.interrupt();设置中断标志位,使得Worker中阻塞中的线程唤醒,此时对于判断 wc > corePoolSize == true的Worker,只要Worker的线程没有从workQueue获取到任务,此时Worker将被线程池回收掉,从而达到了缩容的效果,当然如果队列中一直有任务,corePoolSize也不能立即减少,只能等待workQueue为空时,才会将Worker从线程池中移除,很像我们工作当中 ,如果有一个同事要离职了,还是需要将手头上的事情做完,才去做离职交接工作的,所有的Worker都被设置了中断标识位,对于那些不需要被移除的Worker的线程,中断标识位是如何清除的呢?我们再来看runWorker()方法。
final void runWorker(Worker w) { Thread wt = Thread.currentThread(); Runnable task = w.firstTask; w.firstTask = null; w.unlock(); // allow interrupts boolean completedAbruptly = true; try { while (task != null || (task = getTask()) != null) { w.lock(); // 我们之前只分析了shutdownNow()方法对下面代码块的影响 ,现在我们来分析 // 下面代码块对缩容时的影响,之前我一直在想,对于shutdownNow()方法调用,那些漏网之鱼,只需要 // runStateAtLeast(ctl.get(), STOP) && !wt.isInterrupted() ,就设置 wt.interrupt();即可 // 为什么要做得下面这么复杂呢?现在才明白过来,原来是为了缩容使用 // 对于线程池状态是RUNNING或SHUTDOWN 两种状态时,会调用Thread.interrupted() 清除中断标识 // 从而消除缩容时调用interruptIdleWorkers()方法时,调用了线程的t.interrupt();方法,对于线程的影响 // 而在缩容时,调用所有Worker中的线程的interrupt()主要目的是为了唤醒在take()或poll()中的线程 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); } } finally { task = null; w.completedTasks++; w.unlock(); } } completedAbruptly = false; } finally { processWorkerExit(w, completedAbruptly); } }
我相信此时你对线程缩容已经有了自己的见解。
prestartAllCoreThreads
public int prestartAllCoreThreads() { int n = 0; while (addWorker(null, true)) ++n; return n; }
这个方法的目的,在线程池启动后,直接初始化Worker,加快任务的消费速度。
我相信,你看到这里,对于其他的方法,随便想想,都知道怎么回事,我就不分析了,之前我们有一个知识点没有分析过,就是线程池的拒绝拒绝策略。 在分析源码之前,先来回顾一下线程池有哪此拒绝略。
线程池的拒绝策略
线程池的饱和策略,当阻塞队列满了,且没有空闲的工作线程,如果继续提交任务,必须采取一种策略处理该任务,线程池提供了4种策略:
- AbortPolicy:直接抛出异常,默认策略;
- CallerRunsPolicy:用调用者所在的线程来执行任务;
- DiscardOldestPolicy:丢弃阻塞队列中靠最前的任务,并执行当前任务;
- DiscardPolicy:直接丢弃任务;
final void reject(Runnable command) { handler.rejectedExecution(command, this); }
public static class CallerRunsPolicy implements RejectedExecutionHandler { public CallerRunsPolicy() { } public void rejectedExecution(Runnable r, ThreadPoolExecutor e) { // 用调用者所在的线程来执行任务; 其实就是调用者直接调用任务的run()方法 // 只要线程池的状态是RUNNING if (!e.isShutdown()) { r.run(); } } } public static class AbortPolicy implements RejectedExecutionHandler { public AbortPolicy() { } // 直接抛出RejectedExecutionException异常 public void rejectedExecution(Runnable r, ThreadPoolExecutor e) { throw new RejectedExecutionException("Task " + r.toString() + " rejected from " + e.toString()); } } public static class DiscardPolicy implements RejectedExecutionHandler { public DiscardPolicy() { } // 直接丢弃任务; public void rejectedExecution(Runnable r, ThreadPoolExecutor e) { } } public static class DiscardOldestPolicy implements RejectedExecutionHandler { public DiscardOldestPolicy() { } public void rejectedExecution(Runnable r, ThreadPoolExecutor e) { // 如果线程池状态为RUNNING if (!e.isShutdown()) { // 丢弃阻塞队列中靠最前的任务,并执行当前任务;将队头任务移除掉,再将当前任务加入到队列中 e.getQueue().poll(); e.execute(r); } } }
线程池的常规使用
public static ExecutorService newFixedThreadPool(int nThreads) { return new ThreadPoolExecutor(nThreads, nThreads, 0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<Runnable>()); } public static ExecutorService newFixedThreadPool(int nThreads, ThreadFactory threadFactory) { return new ThreadPoolExecutor(nThreads, nThreads, 0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<Runnable>(), threadFactory); } public static ExecutorService newSingleThreadExecutor() { return new FinalizableDelegatedExecutorService (new ThreadPoolExecutor(1, 1, 0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<Runnable>())); } public static ExecutorService newSingleThreadExecutor(ThreadFactory threadFactory) { return new FinalizableDelegatedExecutorService (new ThreadPoolExecutor(1, 1, 0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<Runnable>(), threadFactory)); } public static ExecutorService newCachedThreadPool() { return new ThreadPoolExecutor(0, Integer.MAX_VALUE, 60L, TimeUnit.SECONDS, new SynchronousQueue<Runnable>()); } public static ExecutorService newCachedThreadPool(ThreadFactory threadFactory) { return new ThreadPoolExecutor(0, Integer.MAX_VALUE, 60L, TimeUnit.SECONDS, new SynchronousQueue<Runnable>(), threadFactory); }
对于newFixedThreadPool线程池,corePoolSize== maximumPoolSize,不会因为队列已满而扩容,newSingleThreadExecutor()线程池,则corePoolSize=maximumPoolSize==1,核心线程数和最大线程数都是1。
newCachedThreadPool()线程池,corePoolSize为0,maximumPoolSize为Integer.MAX_VALUE,当线程空闲时,等待的时间为60秒,阻塞队列为SynchronousQueue。
那我们来分析一下newCachedThreadPool()的整个执行流程。 线程A 调用execute()方法向线程池中提交任务
public void execute(Runnable command) { if (command == null) throw new NullPointerException(); int c = ctl.get(); if (workerCountOf(c) < corePoolSize) { // 将返回false,因为corePoolSize == 0 if (addWorker(command, true)) return; c = ctl.get(); } // 实际上是调用了SynchronousQueue的transfer方法,之前的博客中分析过,offer(command)方法将 // 将返回false if (isRunning(c) && workQueue.offer(command)) { int recheck = ctl.get(); if (! isRunning(recheck) && remove(command)) reject(command); else if (workerCountOf(recheck) == 0) addWorker(null, false); } else if (!addWorker(command, false)) reject(command); }
最后是调用了addWorker(command, false)方法,new了一个Worker来处理当前任务,当然此时线程B,线程C 同样也调用execute()方法来提交任务 ,线程B和线程C一样,都是通过addWorker(command, false)来创建新的任务,此时线程池中有3个Worker,当任务处理完,Worker将调用workQueue.take()方法,将Worker中的线程加入到SynchronousQueue队列中。 ArrayBlockingQueue&LinkedBlockingQueue&DelayQueue&SynchronousQueue&PriorityBlockingQueue源码解析,在队列中有三个线程正在等待线程与之交换任务。此时线程D,线程E,线程F,线程G分别调用execute()方法,向线程池中提交任务,此时workQueue.offer(command)方法将调用成功,线程D,线程E,线程F分别和SynchronousQueue队列交换了任务,之前的三个Worker将继续开始工作,而线程F调用execute()方法,因为SynchronousQueue中无Worker可以与之交换任务,所以线程F调用workQueue.offer(command)方法将返回false,最终只能调用addWorker(command, false) 通过线程池增加任务的方式来处理提交的任务。当然此时所有的Worker处理完任务。等待一分钟之后,没有其他线程向线程池提交任务,所有的Worker将被销毁掉。此时线程池中的Worker数为0,这就是整个newCachedThreadPool()的执行流程。
线程池返回值的获取原理
有时我们需要向线程池中提交一个任务,并获取返回值,这实现原理是什么呢?先来看一个例子。
public class ThreadPoolExecutorTest { public static void main(String[] args) throws Exception { /** * 创建线程池,并发量最大为5 * LinkedBlockingDeque,表示执行任务或者放入队列 */ ThreadPoolExecutor tpe = new ThreadPoolExecutor(5, 10, 0, TimeUnit.SECONDS, new LinkedBlockingDeque<Runnable>(), new ThreadPoolExecutor.CallerRunsPolicy()); Future<String> result = tpe.submit(new Callable<String>() { @Override public String call() throws Exception { return "xxxxxx"; } }); System.out.println(result.get()); } }
从submit()方法进入。请看下面代码。
public <T> Future<T> submit(Callable<T> task) { if (task == null) throw new NullPointerException(); RunnableFuture<T> ftask = newTaskFor(task); execute(ftask); return ftask; } protected <T> RunnableFuture<T> newTaskFor(Callable<T> callable) { return new FutureTask<T>(callable); } public FutureTask(Callable<V> callable) { if (callable == null) throw new NullPointerException(); this.callable = callable; this.state = NEW; // ensure visibility of callable }
从submit()方法来看,最终还是将当前任务FutureTask 通过execute()方法加入到线程池中。 那线程池的Worker最终调用的是FutureTask的run()方法。接下来看run()方法的实现。
public void run() { // 如果当前state != NEW 或 将CAS操作runner 设置为Thread.currentThread()失败 // 则直接返回 ,因为runAndReset()方法也会将runner设置为其自己的线程 if (state != NEW || !UNSAFE.compareAndSwapObject(this, runnerOffset, null, Thread.currentThread())) return; try { Callable<V> c = callable; if (c != null && state == NEW) { V result; boolean ran; try { result = c.call(); ran = true; } catch (Throwable ex) { result = null; ran = false; // 设置state的状态为EXCEPTIONAL setException(ex); } if (ran) set(result); } } finally { // 代码执行到这里,肯定是Thread.currentThread() 获得了方法的执行权限 // 因此不需要CAS 来修改runner的值 runner = null; int s = state; if (s >= INTERRUPTING) // 如果state的状态为INTERRUPTING ,通过Thread.yield(); 释放CPU时间片的方式 // 来加快state更新为INTERRUPTED handlePossibleCancellationInterrupt(s); } } protected void set(V v) { if (UNSAFE.compareAndSwapInt(this, stateOffset, NEW, COMPLETING)) { outcome = v; UNSAFE.putOrderedInt(this, stateOffset, NORMAL); // final state finishCompletion(); } } private void finishCompletion() { for (WaitNode q; (q = waiters) != null;) { // 因为会将所有的waiters的线程都unpark() ,这里将waiters设置为空 if (UNSAFE.compareAndSwapObject(this, waitersOffset, q, null)) { // 只要有线程 for (;;) { Thread t = q.thread; if (t != null) { q.thread = null; // 唤醒当前在等待get()的线程 LockSupport.unpark(t); } WaitNode next = q.next; // 如果next不为空,则继续unpark() q.next的线程 if (next == null) break; q.next = null; q = next; } break; } } done(); callable = null; // to reduce footprint }
线程池调用FutureTask的run()方法,最终调用了我们实现的call()方法,并将call()返回值存储到FutureTask的outcome属性中。接下来看get()方法的实现。
public V get() throws InterruptedException, ExecutionException { int s = state; // 如果线程池还没有调用run()方法,或者当前state不为COMPLETING状态 if (s <= COMPLETING) // 调用get()方法的线程将进入阻塞 s = awaitDone(false, 0L); return report(s); } private int awaitDone(boolean timed, long nanos) throws InterruptedException { final long deadline = timed ? System.nanoTime() + nanos : 0L; WaitNode q = null; boolean queued = false; for (;;) { if (Thread.interrupted()) { removeWaiter(q); throw new InterruptedException(); } int s = state; // 如果state > COMPLETING ,表明outcome = v;已经设置成功,可以直接返回了 if (s > COMPLETING) { if (q != null) q.thread = null; return s; } // 表示outcome = v;还没有设置好,释放CPU时间片,使得 outcome = v; 尽快设置好 else if (s == COMPLETING) Thread.yield(); else if (q == null) q = new WaitNode(); else if (!queued) // 下面包含两步 // q.next = this.waiters // this.waiters = q ,相当于将q作为waiters的头节点插入 queued = UNSAFE.compareAndSwapObject(this, waitersOffset, q.next = waiters, q); else if (timed) { nanos = deadline - System.nanoTime(); // 如果超时都没有得到线程池运行run()方法 ,将outcome 设置为 v,则停止等待 if (nanos <= 0L) { removeWaiter(q); return state; } LockSupport.parkNanos(this, nanos); } else // 阻塞住当前线程,当run()方法调用后, outcome = v; 设置好了,再通知主线程去获取数据 LockSupport.park(this); } } private V report(int s) throws ExecutionException { Object x = outcome; // 如果当前Future返回的state为NORMAL,表示线程池正常执行完,并返回了数据 if (s == NORMAL) return (V)x; // 如果state为CANCELLED,INTERRUPTING,INTERRUPTED,则抛出CancellationException异常 if (s >= CANCELLED) throw new CancellationException(); // 如果state = EXCEPTIONAL,则抛出ExecutionException异常 throw new ExecutionException((Throwable)x); }
cancel()方法的用途还是蛮多的,接下来,我们分析cancel()方法的实现。
public boolean cancel(boolean mayInterruptIfRunning) { // 如果当前state !=null ,则取消失败 if (state != NEW) return false; // 如果state = NEW的情况 if (mayInterruptIfRunning) { // mayInterruptIfRunning = true ,并且state = NEW ,设置当前state 为INTERRUPTING // 如果设置失败,可能run()方法已经被调用了,线程state变为 COMPLETING ,因此直接返回false if (!UNSAFE.compareAndSwapInt(this, stateOffset, NEW, INTERRUPTING)) return false; // 当然也可能线程池的Worker刚调用run()方法,此时state被设置为INTERRUPTING // 但此时runner被设置为Worker的Thread.currentThread(),因此t !=null , // 此时需要调用t.interrupt() 为Worker的线程设置中断标志位 Thread t = runner; if (t != null) t.interrupt(); // 将线程池的状态设置为INTERRUPTED UNSAFE.putOrderedInt(this, stateOffset, INTERRUPTED); // final state } // 如果mayInterruptIfRunning为false,则直接将当前state设置为CANCELLED else if (!UNSAFE.compareAndSwapInt(this, stateOffset, NEW, CANCELLED)) return false; // 唤醒所有在等待get()数据的线程 finishCompletion(); return true; }
ScheduledThreadPoolExecutor
定时线程池类结构图
它用来处理延时任务或定时任务。
它接收SchduledFutureTask类型的任务,是线程池调度任务的最小单位,有三种提交任务 的方式:
- schedule
- scheduledAtFixedRate
- scheduledWithFixedDelay
它采用DelayQueue存储等待的任务
- DelayQueue内部封装了一个PriorityQueue,它会根据time的先后时间排序,若 time相同则根据sequenceNumber排序;
- DelayQueue也是一个无界队列;
SchduledFutureTask SchduledFutureTask接收的参数(成员变量):
- private long time:任务开始的时间
- private final long sequenceNumber;:任务的序号
- private final long period:任务执行的时间间隔
工作线程的执行过程: 工作线程会从DelayQueue取已经到期的任务去执行; 执行结束后重新设置任务的到期时间,再次放回DelayQueue
ScheduledThreadPoolExecutor会把待执行的任务放到工作队列DelayQueue中, DelayQueue封装了一个PriorityQueue,PriorityQueue会对队列中的。
ScheduledThreadPoolExecutor
先来看看ScheduledThreadPoolExecutor的测试。
public class ScheduledThreadPoolExecutorTest { public static void main(String[] args) { ScheduledThreadPoolExecutor scheduled = new ScheduledThreadPoolExecutor(2); scheduled.scheduleAtFixedRate(new Runnable() { @Override public void run() { System.out.println("time " + new Date()); } }, 0, 3, TimeUnit.SECONDS); } } 结果输出 : time Tue Apr 19 10:29:21 CST 2022 time Tue Apr 19 10:29:24 CST 2022
先进入ScheduledThreadPoolExecutor的构造函数。
public ScheduledThreadPoolExecutor(int corePoolSize) { // corePoolSize核心线程数 // 最大线程数 Integer.MAX_VALUE // 如果超出corePoolSize数的Worker,只要从队列中拉取不到数据,立即回收 // DelayedWorkQueue 延迟队列 super(corePoolSize, Integer.MAX_VALUE, 0, TimeUnit.NANOSECONDS, new DelayedWorkQueue()); }
上面的构造函数实际上是调用了ThreadPoolExecutor的构造函数来初始化的。
public ThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue<Runnable> workQueue) { this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue, Executors.defaultThreadFactory(), defaultHandler); }
感觉又回到了我们的老本行,但唯一区别就是使用的阻塞队列不同 。DelayedWorkQueue这个队列其实和DelayQueue的实现原理一模一样,而ArrayBlockingQueue&LinkedBlockingQueue&DelayQueue&SynchronousQueue&PriorityBlockingQueue源码解析 这篇博客中详细分析了DelayQueue的实现原理。因此关于DelayedWorkQueue队列的实现部分,在这篇博客中就不再重复赘述。
我们进入scheduleAtFixedRate方法。
public ScheduledFuture<?> scheduleAtFixedRate(Runnable command, long initialDelay, long period, TimeUnit unit) { // 如果任务为空或 单位为空,则抛出NullPointerException异常 if (command == null || unit == null) throw new NullPointerException(); // 如果期限小于等于 0 ,则抛出 IllegalArgumentException if (period <= 0) throw new IllegalArgumentException(); ScheduledFutureTask<Void> sft = new ScheduledFutureTask<Void>(command, null, triggerTime(initialDelay, unit), unit.toNanos(period)); RunnableScheduledFuture<Void> t = decorateTask(command, sft); sft.outerTask = t; delayedExecute(t); return t; } protected <V> RunnableScheduledFuture<V> decorateTask( Runnable runnable, RunnableScheduledFuture<V> task) { return task; } public FutureTask(Runnable runnable, V result) { this.callable = Executors.callable(runnable, result); this.state = NEW; // ensure visibility of callable } ScheduledFutureTask(Runnable r, V result, long ns, long period) { super(r, result); // 任务加入线程池后多长时间开始运行 this.time = ns; // 第一次任务执行后,每隔多长时间再执行一次任务 , 为什么这么说呢 ? // 假如time = 5 秒,period = 3 秒,当前时间为 2022-04-19 11:18:00 // 1. 第一次任务执行时间 2022-04-19 11:18:05 // 2. 第二次任务执行时间 2022-04-19 11:18:08 // 3. 第三次任务执行时间 2022-04-19 11:18:11 // 3. 第四次任务执行时间 2022-04-19 11:18:14 // ... 依此类推 this.period = period; // 当前任务的序列号,用于compareTo比较,当时间一样的情况下,比较sequenceNumber this.sequenceNumber = sequencer.getAndIncrement(); } private long triggerTime(long delay, TimeUnit unit) { return triggerTime(unit.toNanos((delay < 0) ? 0 : delay)); } long triggerTime(long delay) { return now() + ((delay < (Long.MAX_VALUE >> 1)) ? delay : overflowFree(delay)); } private long overflowFree(long delay) { Delayed head = (Delayed) super.getQueue().peek(); if (head != null) { long headDelay = head.getDelay(TimeUnit.NANOSECONDS); if (headDelay < 0 && (delay - headDelay < 0)) delay = Long.MAX_VALUE + headDelay; } return delay; }
我相信triggerTime()这个方法理解起来,大家有点晕,我们来看一个例子。
private long triggerTime(long delay, TimeUnit unit) { return triggerTime(unit.toNanos((delay < 0) ? 0 : delay)); } long triggerTime(long delay) { return now() + ((delay < (Long.MAX_VALUE >> 1)) ? delay : overflowFree(delay)); } private long overflowFree(long delay) { Delayed head = (Delayed) super.getQueue().peek(); if (head != null) { long headDelay = head.getDelay(TimeUnit.NANOSECONDS); if (headDelay < 0 && (delay - headDelay < 0)) delay = Long.MAX_VALUE + headDelay; } return delay; } 结果输出 :70 如果用Long.MAX_VALUE来举例的话,可能大家被吓晕了,那我们用byte来举例, byte的最大值为127, 最小值为-128 ,如果此时delay=72,但延迟队列中时间最小的为-57,在延迟队列用compareTo()方法比较过程中, 会用-57 - 72 = -129 导致越界,因此作者采用的办法是将delay值改掉,delay = 127 + 57 = 70,再次用 compareTo()方法比较时,70 - 57 = -127 ,不会导致byte越界了,将byte换成Long类型,原理一样, 只是byte理解起来方便 。
上面提到过compareTo()方法,从之前的博客中知道,ScheduledThreadPoolExecutor用到的延迟工作队列实际上是用一棵小顶堆树来实现,因此需要比较两个任务谁的执行时间小。
public int compareTo(Delayed other) { if (other == this) return 0; if (other instanceof ScheduledFutureTask) { ScheduledFutureTask<?> x = (ScheduledFutureTask<?>)other; // time 表示某个具体时间的纳秒值,如2022-04-19 00:00:00 的纳秒值 long diff = time - x.time; if (diff < 0) return -1; else if (diff > 0) return 1; // 如果两个任务的时间一样,则通过sequenceNumber来比较大小值,决定谁先执行 ,时间越小,越先执行 else if (sequenceNumber < x.sequenceNumber) return -1; else return 1; } long d = (getDelay(TimeUnit.NANOSECONDS) - other.getDelay(TimeUnit.NANOSECONDS)); return (d == 0) ? 0 : ((d < 0) ? -1 : 1); }
下面来看,将当前任务添加到延迟队列中。
private void delayedExecute(RunnableScheduledFuture<?> task) { // 如果当前线程池状态是SHUTDOWN,STOP,TIDYING,TERMINATED,则通过拒绝策略来处理任务 if (isShutdown()) reject(task); else { // 将当前任务添加到延迟工作队列中 super.getQueue().add(task); // 如果此时当前线程池状态是SHUTDOWN,STOP,TIDYING,TERMINATED if (isShutdown() && // 如果当前线程池state是不可运行状态 ,则将新加入的task从队列中remove掉 // 如果是 STOP,TIDYING,TERMINATED ,则肯定会将task从队列中移除 // 如果是SHUTDOWN 并且 period> 0 ,并且 continueExistingPeriodicTasksAfterShutdown == true // 则将 task 从队列中移除 // 如果state是SHUTDOWN并且 period == 0 并且 executeExistingDelayedTasksAfterShutdown = true , // 则将task从队列中移除 !canRunInCurrentRunState(task.isPeriodic()) && // 移除已经加入到队列中的task remove(task)) // 将当前任务的状态设置为 CANCELLED task.cancel(false); else ensurePrestart(); } } boolean canRunInCurrentRunState(boolean periodic) { // 下面分两种情况 // 1. 如果period == 0,表示定时任务只执行一次。 // a) executeExistingDelayedTasksAfterShutdown == true ,当然默认为true ,当前线程池状态为RUNNING或SHUTDOWN,则返回true // b) executeExistingDelayedTasksAfterShutdown == false, 可以手动设置 ,当前线程池状态为RUNNING则返回true // 2. 如果period > 0 // a) continueExistingPeriodicTasksAfterShutdown ==true,默认为false,当前线程池状态为RUNNING或SHUTDOWN则返回true // b) continueExistingPeriodicTasksAfterShutdown == false,当前线程池状态为RUNNING,则返回true return isRunningOrShutdown(periodic ? continueExistingPeriodicTasksAfterShutdown : executeExistingDelayedTasksAfterShutdown); } final boolean isRunningOrShutdown(boolean shutdownOK) { int rs = runStateOf(ctl.get()); return rs == RUNNING || (rs == SHUTDOWN && shutdownOK); } // 至少确保有一个Worker在工作 void ensurePrestart() { // 计算当前线程池中的Worker数 int wc = workerCountOf(ctl.get()); if (wc < corePoolSize) addWorker(null, true); // 如果wc == 0 ,则允许添加最大的Worker数为Integer.MAX_VALUE else if (wc == 0) addWorker(null, false); } // 上面这段代码有区分,正常情况下,如果 线程池中Worker数不会超过corePoolSize数, // 但有特殊情况,我们知道maximumPoolSize = Integer.MAX_VALUE, // 如果corePoolSize = 10 ,当前线程池中Worker数为0,突然向线程池中添加了100个任务 // 100个任务同时看到了 当前线程池中的Worker数为0,则会调用addWorker(null, false); // 方法去添加Worker,但addWorker(null, false); 第二个参数为false,意味着 // 只要当前Worker数小于maximumPoolSize数,都可以将Worker添加到线程池中,在这种极端的情况下 // 可能线程池中的Worker数大于 corePoolSize 数。 作者这么做的原因可能也是想让尽可能多的任务分配到Worker来执行 // 从而减少执行时间的延迟
接下来,我们来看其run()方法
public void run() { boolean periodic = isPeriodic(); // 如果当前是不可运行状态,则唤醒在等待get()返回值的线程,同时将任务的状态设置为CANCELLED if (!canRunInCurrentRunState(periodic)) cancel(false); else if (!periodic) // 如果period == 0 ,表示不需要同期性执行的任务,则直接调用FutureTask的run()方法,最终调用了call()方法 ScheduledFutureTask.super.run(); // 如果需要周期性执行的任务,并且重置任务的state状态,设置下一次执行时间 else if (ScheduledFutureTask.super.runAndReset()) { // 重置下一次运行时间 setNextRunTime(); // 当前任务重新入队 reExecutePeriodic(outerTask); } } 1. 如果当前线程池运行状态不可以执行的任务,取消该任务,然后直接返回,否则继续步骤2 2. 如果不是周期性任务,调用FutureTask中的run方法执行,会设置执行结果,然后直接返回,否则执行步骤3 3. 如果是周期性任务,调用FutureTask中的runAndReset方法执行,不会设置执行结果,然后直接返回,否则执行步骤4和步骤5 4. 计算下次执行该任务的具体时间 。 5. 重复执行该任务 protected boolean runAndReset() { if (state != NEW || !UNSAFE.compareAndSwapObject(this, runnerOffset, null, Thread.currentThread())) return false; boolean ran = false; int s = state; try { Callable<V> c = callable; if (c != null && s == NEW) { try { c.call(); // don't set result ran = true; } catch (Throwable ex) { setException(ex); } } } finally { runner = null; s = state; if (s >= INTERRUPTING) handlePossibleCancellationInterrupt(s); } return ran && s == NEW; } private void setNextRunTime() { long p = period; if (p > 0) time += p; else time = triggerTime(-p); }
接下来看当前任务重新入队的逻辑
void reExecutePeriodic(RunnableScheduledFuture<?> task) { // 当前线程池的状态是RUNNING if (canRunInCurrentRunState(true)) { // 将当前任务加入到队列中 super.getQueue().add(task); // 如果此时线程池状态变为不可运行状态,则将任务从队列中移除 if (!canRunInCurrentRunState(true) && remove(task)) task.cancel(false); else // 看是否需要加Worker来处理任务 ensurePrestart(); } }
该方法和delayedExecute方法类似,不同的是:
- 由于调用reExecutePeriodic方法时已经执行过一次周期性任务了,所以不会 reject当前任务;
- 传入的任务一定是周期性任务。
DelayedWorkQueue
ScheduledThreadPoolExecutor之所以要自己实现阻塞的工作队列,是因为 ScheduledThreadPoolExecutor要求的工作队列有些特殊。
DelayedWorkQueue是一个基于堆的数据结构,类似于DelayQueue和 PriorityQueue。在执行定时任务的时候,每个任务的执行时间都不同,所以 DelayedWorkQueue的工作就是按照执行时间的升序来排列,执行时间距离当前时间越近 的任务在队列的前面(注意:这里的顺序并不是绝对的,堆中的排序只保证了子节点的下次 执行时间要比父节点的下次执行时间要大,而叶子节点之间并不一定是顺序的,下文中会说 明)。
总结 主要总结为以下几个方面:
与Timer执行定时任务的比较,相比Timer,ScheduedThreadPoolExecutor有 什么优点;
- ScheduledThreadPoolExecutor继承自ThreadPoolExecutor,所以它也是一 个线程池,也有coorPoolSize和workQueue,ScheduledThreadPoolExecutor特 殊的地方在于,自己实现了优先工作队列DelayedWorkQueue;
- ScheduedThreadPoolExecutor实现了ScheduledExecutorService,所以就有 了任务调度的方法,如schedule,scheduleAtFixedRate和 scheduleWithFixedDelay,同时注意他们之间的区别;
- 内部类ScheduledFutureTask继承自FutureTask,实现了任务的异步执行并且 可以获取返回结果。同时也实现了Delayed接口,可以通过getDelay方法获取将要执 行的时间间隔;
- 周期任务的执行其实是调用了FutureTask类中的runAndReset方法,每次执行 完不设置结果和状态。
- 详细分析了DelayedWorkQueue的数据结构,它是一个基于最小堆结构的优先 队列,并且每次出队时能够保证取出的任务是当前队列中下次执行时间最小的任务。 同时注意一下优先队列中堆的顺序,堆中的顺序并不是绝对的,但要保证子节点的值 要比父节点的值要大,这样就不会影响出队的顺序。
总体来说,ScheduedThreadPoolExecutor的重点是要理解下次执行时间的计算,以及优 先队列的出队、入队和删除的过程,这两个是理解ScheduedThreadPoolExecutor的关 键。
我相信大家已经对从线程池中获取数据的原理也已经理解了,线程池的源码解析也告一段落了,如果有疑问,给我博客下留言。
我相信学习源码就是一种境界的提升,如苏轼所说。
学习有三重境界:
- 第一重境界:看山是山,看水是水。
- 第二重境界:看山不是山,看水不是水。
- 第三重境界:看山依然是山,看水依然是水。
也希望聪明的读者能从源码中领悟到这三重境界。之前看线程池的状态,拒绝策略,这些知识点,你只是知道是这么回事,当你看过源码之后,你就会达到另外一种境界,原来如此,豁然开朗。