线程池源码解析 1.前导_FutureTask源码解析
创始人
2024-01-22 08:10:31

线程池—FutureTask源码解析

简介

  • 在学习线程池之前,需要先学习一下 FutureTask,因为线程池 submit 的结果就是 FutureTask。

  • 那么什么是 FutureTask,其表示一个未来的任务。也就是说这个任务比较耗时,当前调用线程会阻塞拿到这个结果。

  • FutureTask 接口继承体系

    image-20221016191736029

  • FutureTask 实现了 Future 和 Runnable 接口,又能执行,又能拿到执行结果。

  • 所有的通过 submit 方式提交到线程池的任务(Runnable、Callable),进入到队列或被线程执行之前,都会被封装成 FutureTask,然后提交到线程池中。

Future 接口

  • Future 接口表示异步计算的结果,通过 Future 接口提供的方法,可以很方便的查询异步计算任务是否执行完成,获取异步计算的结果,取消未执行的异步任务,或者中断异步任务的执行,接口定义如下:

    public interface Future {boolean cancel(boolean mayInterruptIfRunning);boolean isCancelled();boolean isDone();V get() throws InterruptedException, ExecutionException;V get(long timeout, TimeUnit unit)throws InterruptedException, ExecutionException, TimeoutException;
    }
    

Runnable 接口,就不做过多的解释了。

FutureTask 的使用场景

  • FutureTask 可用于异步获取执行结果或取消执行任务的场景。通过传入 Runnable 或者 Callable 的任务给 FutureTask,直接调用其 run 方法或者放入线程池执行,之后可以在外部通过 FutureTask 的 get 方法异步获取执行结果,因此,FutureTask非常适合用于耗时的计算,主线程可以在完成自己的任务后,再去获取结果。另外,FutureTask 还可以确保即使调用了多次 run 方法,它都只会执行一次 Runnable 或者 Callable 任务,或者通过 cancel 取消 FutureTask 的执行等。
  • 关于 FutureTask 的基本使用可以参考文章:FutureTask的用法

在 JDK1.5 时,增加了一种开启线程的方式,实现 Callable 接口,这种开启线程的方式可以拿到返回值并且可以捕获异常。但是我们必须配合着FutureTask 类使用。FutureTask 类上就是 Runnable 接口的一个实现类。因为最终开启线程还是得通过new Thread().start()的方式开启线程。

线程池返回的 Future

  • 简单的线程池案例

    public class FutureTaskDemo {public static void main(String[] args) throws Exception{ExecutorService threadPool = Executors.newFixedThreadPool(5);Future future = threadPool.submit(new Runnable() {@Overridepublic void run() {System.out.println("FutureTaskDemo.run");}});future.get();}
    }
    
  • 我们可以看到线程池返回的 Future 是 FutureTask

    image.png

开启线程的简单方式

public static void main(String[] args) throws ExecutionException, InterruptedException {// 匿名类的方式构造一个Callable的实现类Callable callable = new Callable() {@Overridepublic Integer call() throws Exception {return 1;}};// 将Callable的实现类传入FutureTask task = new FutureTask<>(callable);// FutureTask就是Runnable的实现类,直接传到构造函数中即可Thread thread = new Thread(task);// 开启线程thread.start();// 阻塞拿到返回值int x = task.get();System.out.println(x);
}

接下来我们来分析一下 FutureTask 的源码,为接下来的线程池源码解析打好基础!

FutureTask 源码

属性

public class FutureTask implements RunnableFuture {// 表示当前task任务的状态 private volatile int state;// 新建状态,当前任务尚未执行	private static final int NEW          = 0;// 当前任务正在结束,尚未完全结束,一种临界状态private static final int COMPLETING   = 1;// 当前任务正常结束private static final int NORMAL       = 2;// 当前任务执行过程中发生了异常,内部封装的callable.run() 向上抛出了异常private static final int EXCEPTIONAL  = 3;// 当前任务被取消private static final int CANCELLED    = 4;// 当前任务中断中private static final int INTERRUPTING = 5;// 当前任务已中断private static final int INTERRUPTED  = 6; /** 线程池调用submit()方法时将Runnable变为Callable(适配器模式)*/private Callable callable;/** 正常情况下:任务执行结束,outcome保存执行结果(callable的返回值)* 非正常情况:callable向上抛出异常,outcome保存异常*/private Object outcome; // 当前任务被线程执行期间,保存当前执行任务的线程引用private volatile Thread runner;// 因为会有很多线程去get当前任务的结果,所以这里使用了一种Stack的数据结构来存储线程private volatile WaitNode waiters;// WaitNode节点static final class WaitNode {// 引用当前线程volatile Thread thread;// 下一个节点volatile WaitNode next;WaitNode() { thread = Thread.currentThread(); }}

构造方法

  • 传入一个 Callable 接口的实现类

        public FutureTask(Callable callable) {if (callable == null)throw new NullPointerException();// 为callable属性赋值 这里的callable就是程序员自己实现的业务类this.callable = callable;// 设置当前任务状态为NEW(0)this.state = NEW;       }
    
  • 传入一个 Runnable 接口的实现类

        public FutureTask(Runnable runnable, V result) {/** 使用适配器模式将runnable转换为了callable 外部线程 通过get获取当前任务执行结果时,结果可能为 null 也可能为 传进来的值* (submit的重载方法中有一个只传入Runnable的方法,而它的result就为null:RunnableFuture ftask = newTaskFor(task, null);)* 底层实际上就是创建了Callable接口的实现类,在call方法里调用了传的runnable的run方法,然后将我们传入的result原生不动的返回了* RunnableAdapter 通过将 Runnable 组合到自身内部,再实现 Callable 接口来完成从 Runnable 到 Callable 的适配*/// 为内部的callable属性赋值this.callable = Executors.callable(runnable, result);// 设置任务为初始状态this.state = NEW;}
    //                                  ||
    //                                  ||
    //                                  \/public static  Callable callable(Runnable task, T result) {if (task == null)throw new NullPointerException();return new RunnableAdapter(task, result);}
    //                         ||
    //                         ||
    //                         \/static final class RunnableAdapter implements Callable {final Runnable task;final T result;RunnableAdapter(Runnable task, T result) {this.task = task;this.result = result;}public T call() {// 直接运行了run方法task.run();// 直接将传来的result返回了 传什么返回什么return result;}}
    
  • 总结

    FutureTask 的构造器主要就是传入一个 Callable 或者 Runnable 的实现类,为内部的 callable 属性赋值,当我们传入的是 Runnable 时,会将我们的 Runnable 通过适配器模式进行包装,包装成一个 Callable,返回值我们自定义(返回值没有意义)。

run()

大致流程解析

  1. 先判断任务的状态是否处于 NEW 的状态,不处于 NEW 的状态直接返回不执行。
  2. 当前线程尝试使用 CAS修改内部的runner的引用 的方式去执行这个任务,CAS 失败说明其他线程抢到了这个任务,当前线程也直接返回。
  3. 当前线程如果尝试 CAS修改runner指向为当前线程 成功就会去执行任务。
  4. 如果任务正常执行(没有出现异常),会拿到执行结果,并且调用 set() 方法,将结果设置到 FutureTask 内部的 outcome 属性中,并将线程状态修改为NORMAL(正常结束),set() 方法调用 finishCompletion() 方法将所有的读线程全部唤醒,最后将 FutureTask 内部的 callable 置为 null。
  5. 如果任务执行过程中出现了异常,首先将执行结果置为 null,然后调用 setException() 方法,先将异常信息设置到 outcome 中,然后将任务状态设置为 EXCEPTIONAL,最终调用 finishCompletion 方法将读线程全部唤醒。

img

    // submit(runnable/callable) -> newTaskFor(runnable) -> execute(task) -> pool// 任务执行入口public void run() {/* * 1.先判断state是否处于NEW状态,非NEW状态的任务线程都不会执行* 2.尝试使用CAS更新内部的runner(Thread)属性为当前线程,CAS失败表示有其他线程竞争这个任务 */if (state != NEW ||!UNSAFE.compareAndSwapObject(this, runnerOffset,null, Thread.currentThread()))return;/** 执行到这里表示当前任务一定是NEW状态 并且 当前线程也抢占任务成功*/try {// 内部的callable为c赋值 就是程序员自己封装逻辑的callable 或者 装饰后的runnableCallable c = callable;/** 条件1:c != null 防止空指针异常* 条件2:state == NEW 防止外部线程 cancel掉当前任务*/if (c != null && state == NEW) {// 保留结果的引用V result;// true  表示callable.run 代码块执行成功 未抛出异常// false 表示callable.run 代码块执行失败 抛出异常boolean ran;try {// 调用程序员自定义的callable或者适配之后的runnable拿到结果result = c.call();// c.call未抛出任何异常,ran会设置为true 代码块执行成功ran = true;} catch (Throwable ex) {// 说明程序员自己写的逻辑块有bug了 抛出异常 将结果置为nullresult = null;// ran置为falseran = false;// 后面讲// 非正常情况下 有异常 会执行这个set方法setException(ex);}// 运行成功if (ran)// 说明当前c.call正常执行结束了// set就是用CAS方式设置任务的状态并将结果设置到outcome中set(result);}} finally {// 将当前线程的引用runner置为nullrunner = null;// 关于中断..后面讲int s = state;if (s >= INTERRUPTING)handlePossibleCancellationInterrupt(s);}}

set()

    /** 使用CAS的方式设置任务状态并将结果写入到outcome中*/ protected void set(V v) {// 使用CAS方式设置当前任务状态为 COMPLETING(1)完成中..// 有没有可能失败呢?外部线程等不及了,直接在set执行CAS之前 将task取消了(很小概率事件)if (UNSAFE.compareAndSwapInt(this, stateOffset, NEW, COMPLETING)) {// 结果赋值给outcomeoutcome = v;// 将结果赋值给outcome之后,马上会将任务状态修改为NORMAL(2)正常结束状态(非cAS,直接操作内存进行写的过程)UNSAFE.putOrderedInt(this, stateOffset, NORMAL); // final state// 后面讲// 猜一猜?最起码得把get()在此阻塞的线程 唤醒..finishCompletion();}}

setException()

    protected void setException(Throwable t) {/** 使用CAS方式设置当前任务状态为 COMPLETING(1)完成中..*/if (UNSAFE.compareAndSwapInt(this, stateOffset, NEW, COMPLETING)) {// 这里的t引用的就是 callable 向上层抛出来的异常,赋值给outcomeoutcome = t;// 修改任务状态为EXCEPTIONAL(3),即出现异常的状态UNSAFE.putOrderedInt(this, stateOffset, EXCEPTIONAL);// 讲完get()再说finishCompletion();}}

get()

大致流程

  • 先获取任务的状态 state,如果说任务处于NEW 或者 COMPLETING(未完全完成),会进入 awaitDone() 方法进行挂起。否则说明任务已完成,调用 report() 方法,取出 run 方法完成时向 outcome 写入的结果或者异常信息进行返回,可能抛出异常。
  /** 场景:多个线程等待当前任务执行完成后的结果*/public V get() throws InterruptedException, ExecutionException {int s = state;/** 条件成立:当前任务未执行、正在执行、正完成 * 调用get()的外部线程(非runner线程)会被阻塞在get()方法上*/if (s <= COMPLETING)// 返回当前任务的状态(可能当前线程已经在里面睡了一会了..),如果内部抛出中断异常,get()方法也会向上抛// awaitDone 实现阻塞的方法 FutureTask最核心的地方s = awaitDone(false, 0L);// 返回结果return report(s);}

awaitDone()

整个方法是一个自旋

(下面的第一次第二次第三次的前提条件时任务状态没有完成并且不处于接近完成时)

  1. 第一次进入会将当前线程构造成一个 WaitNode 节点。
  2. 第二次进入会将构造出来的节点入栈(头插头出的队列)
  3. 第三次进入会调用 LockSupport 当前线程挂起
  4. 当当前线程被唤醒时,首先会判断唤醒的原因是否是因为其他线程对当前线程进行了中断操作,如果是的话,就将当前线程对应的WaitNode节点出栈,然后抛出中断异常。
  5. 判断当任务状态处于接近完成的状态,那么就释放 CPU,然后进入下一次的抢占过程中。
  6. 当任务处于已完成时,将 WaitNode 中的线程属性设置为 null,最终返回任务的最终状态state
 	 /** 返回值是任务的状态*/private int awaitDone(boolean timed, long nanos)throws InterruptedException {// 假设此次是不带超时的方法 拿到的时间为0final long deadline = timed ? System.nanoTime() + nanos : 0L;// 引用当前线程 封装成WaitNode对象WaitNode q = null;// 表示当前线程WaitNode对象有没有入队/压栈boolean queued = false;// 自旋for (;;) {/** 进入这个if 说明当前线程已经被阻塞后唤醒了,并且是被其他线程使用中断的方式唤醒的, * interrupted()返回true后会将Thread的中断标记重置为false(即下一次再调用此方法时就会返回false)*/if (Thread.interrupted()) {// 将Node节点出栈removeWaiter(q);// get方法抛出中断异常throw new InterruptedException();}/** 线程被阻塞(挂起)后,被其他线程使用unpark()正常唤醒,会正常自旋,走下面逻辑*/ // 读取当前任务的最新状态int s = state;// 条件成立:表示当前任务已经有结果了.. 可能是好 可能是坏if (s > COMPLETING) {// 条件成立:说明已经为当前线程创建过node了,此时需要将node.thread = nullif (q != null)q.thread = null; // help GCreturn s; // 返回当前状态}// 条件成立:说明任务接近完成状态,这里让当前线程释放CPU,进入下一次抢占CPU的状态else if (s == COMPLETING) // cannot time out yetThread.yield();/** 第一次自旋:当前线程还未创建WaitNode对象,此时根据当前线程封装一个WaitNode对象*/ 	else if (q == null)q = new WaitNode();/** 第二次自旋:WaitNode对象已经创建,但是node对象还未入队*/else if (!queued)// q.next = waiters 当前线程node节点 next指向原队列的头节点(waiters一直指向队列的头)// CAS方式设置waiters引用指向 当前线程node(队列头结点),成功的话 queued == true 否则,可能其它线程先你一步入队了queued = UNSAFE.compareAndSwapObject(this, waitersOffset, q.next = waiters, q);/** 第三次自旋:就是直接挂起当前线程*/// 带有超时的挂起else if (timed) {nanos = deadline - System.nanoTime();if (nanos <= 0L) {removeWaiter(q);return state;}LockSupport.parkNanos(this, nanos);}// 不带超时的挂起else// 当前get()操作的线程就会被park掉(阻塞) 线程状态变为WAITING状态 相当于休眠了// 除非有其他线程将你唤醒 或者 将当前线程中断LockSupport.park(this);}}

removeWaiter()

    private void removeWaiter(WaitNode node) {if (node != null) {// 将node内部的thread置为nullnode.thread = null; // 自旋retry:for (;;) {/** 遍历链表,将指定节点删除,可能有些节点因为中断没有出队,但是内部的thread被设置为null了(见上面的方法)* 也将这些节点一起出队*//** pred 当前节点的前一个节点* q    当前节点* s    当前节点的下一个节点* 循环条件为:当前节点不为空* 每次将q指向下一位*/for (WaitNode pred = null, q = waiters, s; q != null; q = s) {s = q.next; // 赋值if (q.thread != null)pred = q; // pred指向q 开始下一轮循环// 前置条件:当前节点为null// 条件成立:说明当前节点不是头结点else if (pred != null) {// 将前置节点的指针指向下一个节点 相当于删除当前节点pred.next = s;// 如果前置节点此时也为空 直接continue再次进行自旋if (pred.thread == null) // check for racecontinue retry;}// 说明当前节点是头结点 进行CAS替换操作 将waiters当前节点的下一个节点else if (!UNSAFE.compareAndSwapObject(this, waitersOffset,q, s))continue retry;}break; // 全部处理完则退出自旋}}}

report()

  • 去获取 run() 方法结束后存储在 outcome 中的结果并返回
    /** 处理 get 方法的返回结果*/private V report(int s) throws ExecutionException {/** 正常情况下,outcome保存的是callable运行结束的结果* 非正常情况下,outcome保存的是callable抛出的异常对象*/Object x = outcome;// 条件成立:当前任务状态正常结束if (s == NORMAL)// 直接返回callable运算结果return (V)x;// 任务被结束if (s >= CANCELLED)// 抛出取消异常throw new CancellationException();// 执行到这,说明callable接口实现中,是有bug的.. 抛出异常throw new ExecutionException((Throwable)x);}

finishCompletion()

  • 大致流程就是遍历队列中的所有 WaitNode 节点,将所有等待的线程全部唤醒,并将所有的 WaitNode 设置为null。
	/** 在 set 方法中调用该方法*/private void finishCompletion() {// q指向waiters链表的头结点for (WaitNode q; (q = waiters) != null;) {/** 使用CAS设置waiters为null,是因为怕外部线程使用cancel取消当前任务也会触发finishCompletion(小概率事件)*/if (UNSAFE.compareAndSwapObject(this, waitersOffset, q, null)) {// 自旋for (;;) {// 获取当前node节点封装的threadThread t = q.thread;// 条件成立,说明当前线程不为nullif (t != null) {q.thread = null; // help GC// 唤醒当前节点对应的线程LockSupport.unpark(t); }// next 当前节点的下一个节点WaitNode next = q.next;// 说明当前的节点时最后一个节点if (next == null)break; // 说明都唤醒完成 直接退出自旋q.next = null; // help gcq = next; // 指向下一个节点}break; // 此时也说明全部处理完成 跳出最外层自旋}}// done接口 里面什么都没有实现 是一个可以进行扩展的方法done();// 将callable设置为null help GCcallable = null;        // to reduce footprint}

cancel()

  • cancel 方法也会调用 finishCompletion 方法。
    /* * 如果通过线程池submit提交了任务之后,这个任务迟迟没有结束,这时在外部可以通过future句柄调用它的 cancel 方法,尝试中断/取消当前任务* @param:mayInterruptIfRunning* 		   true->如果当前任务处于正在运行状态,会给线程发一个中断信号*         false->直接改变当线程状态* @return:cancel成功则返回true 否则返回false*/public boolean cancel(boolean mayInterruptIfRunning) {/* * 条件1:state == NEW 成立,表示当前任务处于运行中 或者 处于线程池 任务队列中..* 条件2:UNSAFE.compareAndSwapInt(this, stateOffset, NEW, mayInterruptIfRunning ? INTERRUPTING : CANCELLED))* CAS操作 尝试修改线程状态为中断或者是取消(看传来的参数)* 条件成立:说明修改状态成功,可以去执行下面逻辑了,否则 返回false 表示cancel失败*/if (!(state == NEW &&UNSAFE.compareAndSwapInt(this, stateOffset, NEW,mayInterruptIfRunning ? INTERRUPTING : CANCELLED)))return false;try {    // true,将线程设置为中断状态if (mayInterruptIfRunning) {try {// 执行任务的线程 有可能现在是null// null的情况就是:当前任务还在队列中,还没有线程去获取它Thread t = runner;// 条件成立:说明当前线程runner正在执行taskif (t != null)/** 调用线程的中断方法,如果程序是响应中断的话,会走中断逻辑* 假设程序不是响应中断的,那么啥也不会发生*/t.interrupt();} finally { // final state// 中断完成后将状态改为中断完成UNSAFE.putOrderedInt(this, stateOffset, INTERRUPTED);}}} finally {// 调用 finishCompletion() 完成线程 会唤醒所有get阻塞的线程finishCompletion();}return true;}

参考

  • 视频参考
    • b站_小刘讲源码公开课【面试必问】硬核手撕Java线程池FutureTask源码
  • 文章参考
    • shstart7_线程池源码解析1.FutureTask源码解析
    • 兴趣使然的草帽路飞_线程池源码分析_01 FutureTask源码分析
    • 肆华_FutureTask阅读理解

相关内容

热门资讯

埃菲尔铁塔在哪 中国仿建埃菲尔... 2019年4月26日,广西南宁市,街头惊现一座巨型山寨版埃菲尔铁塔,高约20米,白色塔身,造型逼真,...
ps局部放大 放大某个部位的图... 软件类学习建议:边看边练习,可以先收藏后学习。一、使用Ctrl快捷键放大:Ctrl + +缩小:Ct...
苗族的传统节日 贵州苗族节日有... 【岜沙苗族芦笙节】岜沙,苗语叫“分送”,距从江县城7.5公里,是世界上最崇拜树木并以树为神的枪手部落...
北京的名胜古迹 北京最著名的景... 北京从元代开始,逐渐走上帝国首都的道路,先是成为大辽朝五大首都之一的南京城,随着金灭辽,金代从海陵王...
应用未安装解决办法 平板应用未... ---IT小技术,每天Get一个小技能!一、前言描述苹果IPad2居然不能安装怎么办?与此IPad不...
脚上的穴位图 脚面经络图对应的... 人体穴位作用图解大全更清晰直观的标注了各个人体穴位的作用,包括头部穴位图、胸部穴位图、背部穴位图、胳...
长白山自助游攻略 吉林长白山游... 昨天介绍了西坡的景点详细请看链接:一个人的旅行,据说能看到长白山天池全凭运气,您的运气如何?今日介绍...
猫咪吃了塑料袋怎么办 猫咪误食... 你知道吗?塑料袋放久了会长猫哦!要说猫咪对塑料袋的喜爱程度完完全全可以媲美纸箱家里只要一有塑料袋的响...
世界上最漂亮的人 世界上最漂亮... 此前在某网上,选出了全球265万颜值姣好的女性。从这些数量庞大的女性群体中,人们投票选出了心目中最美...
埃菲尔铁塔在哪 中国仿建埃菲尔... 2019年4月26日,广西南宁市,街头惊现一座巨型山寨版埃菲尔铁塔,高约20米,白色塔身,造型逼真,...
ps局部放大 放大某个部位的图... 软件类学习建议:边看边练习,可以先收藏后学习。一、使用Ctrl快捷键放大:Ctrl + +缩小:Ct...
苗族的传统节日 贵州苗族节日有... 【岜沙苗族芦笙节】岜沙,苗语叫“分送”,距从江县城7.5公里,是世界上最崇拜树木并以树为神的枪手部落...
北京的名胜古迹 北京最著名的景... 北京从元代开始,逐渐走上帝国首都的道路,先是成为大辽朝五大首都之一的南京城,随着金灭辽,金代从海陵王...