问答文章1 问答文章501 问答文章1001 问答文章1501 问答文章2001 问答文章2501 问答文章3001 问答文章3501 问答文章4001 问答文章4501 问答文章5001 问答文章5501 问答文章6001 问答文章6501 问答文章7001 问答文章7501 问答文章8001 问答文章8501 问答文章9001 问答文章9501

Java多线程专题之Callable、Future与FutureTask(含源码分析)

发布网友 发布时间:2024-09-26 05:53

我来回答

1个回答

热心网友 时间:2024-10-04 18:37

前言

大家好,一直以来我都本着用最通俗的话理解核心的知识点, 我认为所有的难点都离不开 基础知识 的铺垫。目前正在出一个Java多线程专题长期系列教程,从入门到进阶, 篇幅会较多, 喜欢的话,给个关注?? ~

适合人群

有一定的Java基础

想学习或了解多线程开发

想提高自己的同学

大佬可以绕过 ~

背景

之前给大家讲了一些框架的使用,这些都属于业务层面的东西,你需要熟练掌握它并在项目中会运用它即可,但这些对自身技术的积累是远远不够的,如果你想要提高自己,对于语言本身你需要花更多的时间去挖掘而不是局限于框架的使用,所以之前为什么跟大家一直强调基础的重要性,框架可以千变万化,层出不穷,但是基础它是不变的,不管是学java还是前端或者是其它语言, 这一点大家还是需要认清的。

接下来的几期会专门讲多线程这一块,篇幅会较多,耐心看完你一定会有收获~

情景回顾

上期带大家学习了什么是进阶学习了Thread以及分析了它的一些源码,本期带大家学习Callable、Future与FutureTask的用法以及源码分析, 内容较多, 我们一起来看一下吧~

Callable & Future

之前我们通过Runnable,Thread就可以创建一个线程,但是它也有一个局限,就是没有返回值,有时候我们的需求需要结合多任务处理后的数据做一些事情,所以通过上边的方法就不好解决了。

下面我们看一下Callable

public interface Callable<V> {/** * Computes a result, or throws an exception if unable to do so. * * @return computed result * @throws Exception if unable to compute a result */V call() throws Exception;}

首先它是一个接口,且还提供了泛型的支持,call方法有返回值, 那怎么使用它呢,肯定是要实现它

public class CallableTest {public static class CallableDemo implements Callable<String> {@Overridepublic String call() throws Exception {return "hello";}}public static void main(String[] args) throws Exception {CallableDemo demo = new CallableDemo();String result = demo.call();System.out.println(result);System.out.println("main");}}

运行一下实际输出

hellomain

发现返回的结果输出出去了,但是这里有个问题,这个main输出在hello之后,似乎好像没有开启一个线程,依然是同步执行的,是这样吗,我们看一下call内部的线程环境

public String call() throws Exception {System.out.println(Thread.currentThread());Thread.sleep(3000);return "hello";}

运行一下实际输出

Thread[main,5,main]hellomain

好家伙,还是main线程内部,并且线程还被阻塞了,原来new是开启不了线程的,只是单纯的实现了一下它的接口,我们姿势搞错了。其实它的源码上加了注释的,说通常会借助Excutors类使用,这个类是用来创建线程池的,这个我们后边讲,这里给大家演示一下

public static void main(String[] args) throws Exception {CallableDemo demo = new CallableDemo();// 创建线程池ExecutorService executor = Executors.newCachedThreadPool();// 提交任务Future<String> future = executor.submit(demo);System.out.println("main");}

实际输出:

mainThread[pool-1-thread-1,5,main]

发现是单独线程执行的,并且没有阻塞线程。我们发现这里也用到了Future,这个翻译过来时未来的意思,这里也就是结果发生在后边,它是一个异步情况, 那么我们如何获取到结果呢?

System.out.println(future.get());System.out.println("main");

实际输出:

Thread[pool-1-thread-1,5,main]hellomain

发现结果拿到了,但是运行的时候好像线程被阻塞了,我们可以发现get()会导致线程阻塞,举一反三,我想不阻塞的情况下拿到返回值,可以吗?那有什么办法呢?开启单独的线程不就好了,那么在单独的线程可以拿到其它线程的值吗,我们来试一下

new Thread(() -> {try {System.out.println(future.get());} catch (InterruptedException e) {e.printStackTrace();} catch (ExecutionException e) {e.printStackTrace();}}).start();System.out.println("main");

实际运行输出:

public class CallableTest {public static class CallableDemo implements Callable<String> {@Overridepublic String call() throws Exception {return "hello";}}public static void main(String[] args) throws Exception {CallableDemo demo = new CallableDemo();String result = demo.call();System.out.println(result);System.out.println("main");}}0

发现,这下就对了~

Future & FutureTask 源码解析

端起小板凳,这部分好好听,我们主要看下它的源码实现。我们上文使用到了 Future,我们看一下它的定义,发现它也是一个接口

public class CallableTest {public static class CallableDemo implements Callable<String> {@Overridepublic String call() throws Exception {return "hello";}}public static void main(String[] args) throws Exception {CallableDemo demo = new CallableDemo();String result = demo.call();System.out.println(result);System.out.println("main");}}1

还有一个接口叫做RunnableFuture,FutureTask是它的一个实现类,这个类帮我实现了很多好用的方法,因为我们自己实现的话是很麻烦的

public class CallableTest {public static class CallableDemo implements Callable<String> {@Overridepublic String call() throws Exception {return "hello";}}public static void main(String[] args) throws Exception {CallableDemo demo = new CallableDemo();String result = demo.call();System.out.println(result);System.out.println("main");}}2

之前的例子也可以用FutureTask改写成:

public class CallableTest {public static class CallableDemo implements Callable<String> {@Overridepublic String call() throws Exception {return "hello";}}public static void main(String[] args) throws Exception {CallableDemo demo = new CallableDemo();String result = demo.call();System.out.println(result);System.out.println("main");}}3

它继承了 Runnable, Future接口,我们之前调用的get方法就是其中之一,来一起看一下这个get是如何拿到值的,该部分源码来自FutureTask类实现

public class CallableTest {public static class CallableDemo implements Callable<String> {@Overridepublic String call() throws Exception {return "hello";}}public static void main(String[] args) throws Exception {CallableDemo demo = new CallableDemo();String result = demo.call();System.out.println(result);System.out.println("main");}}4

这个state线程的状态值,这里很好理解,一个是阻塞方法awaitDone,一个是抛出结果report,我们重点看一下awaitDone的实现:

public class CallableTest {public static class CallableDemo implements Callable<String> {@Overridepublic String call() throws Exception {return "hello";}}public static void main(String[] args) throws Exception {CallableDemo demo = new CallableDemo();String result = demo.call();System.out.println(result);System.out.println("main");}}5

首先它是一个内部方法,timed指定是否定时等待,如果传true的话需要指定时间nanos

public class CallableTest {public static class CallableDemo implements Callable<String> {@Overridepublic String call() throws Exception {return "hello";}}public static void main(String[] args) throws Exception {CallableDemo demo = new CallableDemo();String result = demo.call();System.out.println(result);System.out.println("main");}}6

WaitNode q = null;它是一个链表结构 volatile 被用来修饰会被不同线程访问和修改的变量, 后边还会讲到,此处先有个印象

public class CallableTest {public static class CallableDemo implements Callable<String> {@Overridepublic String call() throws Exception {return "hello";}}public static void main(String[] args) throws Exception {CallableDemo demo = new CallableDemo();String result = demo.call();System.out.println(result);System.out.println("main");}}7

for (;;) {...},这是一个死循环,这里就是阻塞部分了,内部先会判断线程状态

public class CallableTest {public static class CallableDemo implements Callable<String> {@Overridepublic String call() throws Exception {return "hello";}}public static void main(String[] args) throws Exception {CallableDemo demo = new CallableDemo();String result = demo.call();System.out.println(result);System.out.println("main");}}8

这里为什么会移除呢,想想看,如果不移除,内部积累太多,每次都要遍历它,如果是有竞争的情况下,是不是很浪费。这里主要是避免不必要的高额开销

public class CallableTest {public static class CallableDemo implements Callable<String> {@Overridepublic String call() throws Exception {return "hello";}}public static void main(String[] args) throws Exception {CallableDemo demo = new CallableDemo();String result = demo.call();System.out.println(result);System.out.println("main");}}9

这里为什么移除?因为完成了,我只要结果就好了,不需要在进一步判断了

hellomain0

如果处于COMPLETING,会让出cpu时间

hellomain1

这个很好理解,节点不存在就创建一个

hellomain2

如果有新任务进来,会新建一个节点,然后利用CAS操作放入waiter链表的头部,这里是一个原子性操作,CAS的概念我们后边给大家讲,这里一切都是为了安全

compareAndSwap是个原子方法,原理是CAS,即将内存中的值与期望值进行比较,如果相等,就将内存中的值修改成新值并返回true。

hellomain3

这里判断消亡时间,如果超时了,移除节点,并返回线程状态,LockSupport使线程阻塞,有的同学可能会问,for不是已经阻塞了吗?那为啥还调用LockSupport,这里其实是线程优化,想想你一直for循环一直判断是不是也会产生开销,加上LockSupport避免不要的操作,其实for的整个过程是实现了自旋锁的操作。

阻塞了不就没法执行了吗,park加锁方法还有一个对应的unpark相当于释放锁,但此处没有看到这个方法,那么它在哪个地方呢?我们大体应该可以猜到,它应该是在执行阶段,还记得RunnableFuture接口下的run方法吗?下面我们看一下它的实现

hellomain4

下面我们重点看一下这个set方法

hellomain5

UNSAFE类是一个很特殊的类,它的内部几乎都是native方法,它可以使得我们能够操作内存空间来获得更高的性能,但一般我们很少使用它,因为它不被gc控制,使用不当jvm可能都会挂了。我们重点关注一下 finishCompletion这个方法

hellomain6

我们可以看到在这个内部它是调了一个unpark方法的,可以看出之前awaitDone()方法内部的线程阻塞在这个地方被唤醒了, 再回回过头看awaitDone()方法,就明白为啥要调用park方法了,因为线程没有达到大于COMPLETING状态,它会一直for

最后一个就是report了,返回值

hellomain7

于是我们的get就拿到返回值了

FutureTask 状态

这里给大家补充一下FutureTask的状态值

hellomain8

state可能的状态转变路径如下:

NEW -> COMPLETING -> NORMAL

NEW -> COMPLETING -> EXCEPTIONAL

NEW -> CANCELLED

NEW -> INTERRUPTING -> INTERRUPTED

结束语

本期到这里就结束了, 总结一下,本节主要讲了Callable、Future与FutureTask的常用方法,以及从问题触发,带大家分析了一下FutureTask的源码,这里大家要好好理解,不要去背,想要告诉大家的是学习要带着问题, 看源码一定要大胆猜测,冷静分析 ~

下期预告

之前提到过线程组的概念,下期就带大家学习线程组和线程的优先级。关注公众号加群,一起学习进步。关注我,不迷路, 下期不见不散 ~

更文时间

工作日(周一 ? 周五)

周末不更 ??

节假日不定时更

往期内容

Java多线程专题之线程与进程概述

Java多线程专题之线程类和接口入门

Java多线程专题之进阶学习Thread(含源码分析)

我的博客(阅读体验较佳)

写给初学者的Java基础教程

一文带你快速学习Java集合类

花几分钟快速了解一下泛型与枚举

Java注解与反射入门到进阶

JavaIO教程从入门到进阶

项目源码(源码已更新 欢迎star??)

java-thread-all

地址: https://github.com/qiuChengleiy/java-thread-all.git

推荐 SpringBoot & SpringCloud (源码已更新 欢迎star??)

springboot-all

地址: https://github.com/qiuChengleiy/springboot-all.git

SpringBoot系列教程合集

一起来学SpringCloud合集

原文:https://juejin.cn/post/7103785317173297189
声明声明:本网页内容为用户发布,旨在传播知识,不代表本网认同其观点,若有侵权等问题请及时与本网联系,我们将在第一时间删除处理。E-MAIL:11247931@qq.com
如何理解“时间就是空间,空间就是时间”? 办公室副主任竞聘演讲稿范文 学校办公室主任竞聘演讲稿范文 最新办公室主任竞聘演讲稿 办公室主任竞聘演讲稿优秀范文 ...堆墨现象,墨线 接地和粘度都正常,请问应如何解决 ...都正常了开始喷印结果喷一条墨线好恐怖怎么解决呀各位老师 威力喷码机 求一份学生会演讲词 大学学生会个人优秀演讲稿 求小我和萧的番外 萧娴宁又逃了~~求哦亲 sj131520@qq.com 求gl萧娴宁又逃了番外 Java for循环改写和判断(这个提问规范好烦啊) 求gl小我和萧的番外《萧娴宁又逃了》 邮箱2223921032@qq.com。谢谢_百... 大学一年毁掉一个家我现在好想结束自己 BELMOCA胶囊咖啡机真的好用吗? 萧娴宁又逃了gl在线 谁可以推荐一些较好的胶囊咖啡机品牌给我参考下?声明请别打广告,谢谢... ...用上咖啡管了都,有种家用胶囊咖啡机在家也可以喝咖啡?? ...尿毒症在医院做血透,这病危险吗?寿命能活几年? 尿毒症患者没有了小便现在做血透请问这情况大概能活多久 html代码,为什么我在表格里放一个背景图片,但是预览时看不见,请问怎么... ...这一点引发了你的哪些联想和思考?请自拟题目,600字记叙文。_百度知... 海马和什么煮汤最好呢 排骨炖海龙要怎么炖呢 月前后天津高速货车禁行吗如果进入时是免费出来时不免费会如何_百度知 ... 凌晨三点多是什么时辰 耐克和阿迪鞋码哪个大 阿迪达斯和耐克的衣服买的时候需要买大一号吗? 很久以前看过的一部特工题材的欧美连续剧 belmoca胶囊咖啡机冲出来只有半杯,怎么回事? 网络传播渠道有哪些 抖音喜欢太多了翻不到底怎么一健删除 新阳办健康证身份证复印件要盖章吗 借条到期不还钱多久内要起诉? ...工程量采用2017年辽宁土建定额标准结算总价下浮17%的价格为合同总价... 天津一汽威志V5右前门升降器自己降怎么回事? 怎样去掉微信状态的小红点提醒? 威志升降中控在哪里 威志升降器中控盒在什么地方 威志威V5报节气门电机Off异常,怎么修。我们把车窗控制器的电脑板用... 微信小号怎样弄出来 怎么关掉微信状态红点提醒呢? 梦见跟老公又结婚了的预兆 十二星座缘份最佳配对组合 ,谁是你的灵魂伴侣? 婚后感情仍炽热,哪些星座最易和老婆感情你侬我侬,有精神共鸣? 这些星座配对是伴侣更是无话不说的朋友 ...精神,享受跌宕起伏的剧情,行走在冰与火之间的星座都有哪些?_百度知 ... 肉松适合几个月宝宝吃 宝宝辅食肉松怎么吃 宝宝几个月可以吃肉松