什么是Future接口

Future是java.util.concurrent.Future,是Java提供的接口,可以用来做异步执行的状态获取,它避免了异步任务在调用者那里阻塞等待,而是让调用者可以迅速得到一个Future对象,

后续可以通过Future的方法来获取执行结果。一个实例代码如下:

 1 public class Test {
2 public static void main(String[] args) throws ExecutionException, InterruptedException {
3 //创建线程池
4 ExecutorService executor = Executors.newCachedThreadPool();
5 Future future = executor.submit(new Task());
6 //这一步get会阻塞当前线程
7 System.out.println(future.get());
8
9 executor.shutdown();
10 }
11
12 private static class Task implements Callable<Integer> {
13
14 @Override
15 public Integer call() throws Exception {
16 System.out.println("子线程在进行计算");
17 Thread.sleep(2000);
18 return 1;
19 }
20
21 }
22
23 }

代码很简单,就是将一个Runnable、Callable的实例放到一个线程池里,就会返回一个Future对象。后续通过future.get()取得执行结果,但事实上代码并没有达到异步回调的结果,而是get时阻塞了。

Future原理

因为阅读源码东西太对,这里只是总结关键点,说太多也记不住,先看ExecutorService的submit接口定义,代码如下:

1  * @param task the task to submit
2 * @param <T> the type of the task's result
3 * @return a Future representing pending completion of the task
4 * @throws RejectedExecutionException if the task cannot be
5 * scheduled for execution
6 * @throws NullPointerException if the task is null
7 */
8 <T> Future<T> submit(Callable<T> task);

简单分析:

入参是callable的实例,这个没用疑问
返回参数是Future对象
看代码实现类AbstractExecutorService:

1   public Future<?> submit(Runnable task) {
2 if (task == null) throw new NullPointerException();
3 RunnableFuture<Void> ftask = newTaskFor(task, null);
4 execute(ftask);
5 return ftask;
6 }
1  public FutureTask(Runnable runnable, V result) {
2 this.callable = Executors.callable(runnable, result);
3 this.state = NEW; // ensure visibility of callable
4 }

新建了一个FutureTask对象,状态state是NEW。可能的状态转换是:

Possible state transitions:
* NEW -> COMPLETING -> NORMAL
* NEW -> COMPLETING -> EXCEPTIONAL
* NEW -> CANCELLED
* NEW -> INTERRUPTING -> INTERRUPTED
继续,之后执行的就是FutureTask的run方法,代码如下:

 1 public void run() {
2 if (state != NEW ||
3 !UNSAFE.compareAndSwapObject(this, runnerOffset,
4 null, Thread.currentThread()))
5 return;
6 try {
7 Callable<V> c = callable;
8 if (c != null && state == NEW) {
9 V result;
10 boolean ran;
11 try {
12 result = c.call();
13 ran = true;
14 } catch (Throwable ex) {
15 result = null;
16 ran = false;
17 setException(ex);
18 }
19 if (ran)
20 set(result);
21 }
22 } finally {
23 // runner must be non-null until state is settled to
24 // prevent concurrent calls to run()
25 runner = null;
26 // state must be re-read after nulling runner to prevent
27 // leaked interrupts
28 int s = state;
29 if (s >= INTERRUPTING)
30 handlePossibleCancellationInterrupt(s);
31 }
32 }

我们看上面的代码,分析一下:

先判断state状态,如果不是NEW说明执行完毕,直接return掉。
后面使用CAS操作,判断这个任务是否已经执行,这里FutureTask有个全局的volatile runner字段,这里通过cas将当前线程指定给runner。
这里可以防止callable被执行多次。

继续往下跟,查看finishCompletion方法:
FutureTask中有一个WaiteNode单链表,当执行futureTask.get()方法时,多个线程会将等待的线程的next指向下一个想要get获取结果的线程。
finishCompletion主要就是使用Unsafe.unpark()进行唤醒操作,代码如下:

 1  /**
2 * Removes and signals all waiting threads, invokes done(), and
3 * nulls out callable.
4 */
5 private void finishCompletion() {
6 // assert state > COMPLETING;
7 for (WaitNode q; (q = waiters) != null;) {
8 if (UNSAFE.compareAndSwapObject(this, waitersOffset, q, null)) {
9 for (;;) {
10 Thread t = q.thread;
11 if (t != null) {
12 q.thread = null;
13 LockSupport.unpark(t);
14 }
15 WaitNode next = q.next;
16 if (next == null)
17 break;
18 q.next = null; // unlink to help gc
19 q = next;
20 }
21 break;
22 }
23 }
24
25 done();
26
27 callable = null; // to reduce footprint
28 }

总结一下:

并发原子操作仍旧是利用的CAS原子比较,主要是unsafe类
线程的阻塞、等待、唤醒仍旧是利用类似阻塞队列的链表,里面维护一个链表结构,看链表节点定义:

 1 /**
2 * Simple linked list nodes to record waiting threads in a Treiber
3 * stack. See other classes such as Phaser and SynchronousQueue
4 * for more detailed explanation.
5 */
6 static final class WaitNode {
7 volatile Thread thread;
8 volatile WaitNode next;
9 WaitNode() { thread = Thread.currentThread(); }
10 }

FutureTask的get方法是阻塞的,利用自旋实现,也是最常用的方式,代码如下:

记住一点:JDK底层很多实现都是基于下面几个技术:

JDK底层如何控制并发,保证原子性------------CAS操作
JDK并发如何阻塞、唤醒线程--------------------单向链表或者双向链表队列,队列节点waitnode就是线程的id、状态、next节点等
JDK如何实现自旋操作,比如FutureTask的get方法----------------没有那么神奇,就是for循环等待
JDK如何共享线程数据-----------voliate
JDK如何隔离线程数据-------------ThreadLocal

Future的不足

Future其实是一种模式,如下图:

future很明显,虽然是异步执行,但是无法准确知道异步任务说明时候执行完毕,如果调用get方法,在异步没有执行完成时,还是阻塞;如果频繁get检测,效率不高。

所以,我理解,使用future的get操作应该在最后一步,其他操作都已经完成了,一个可以参考的例子:

 1  private int awaitDone(boolean timed, long nanos)
2 throws InterruptedException {
3 final long deadline = timed ? System.nanoTime() + nanos : 0L;
4 WaitNode q = null;
5 boolean queued = false;
6 for (;;) {
7 if (Thread.interrupted()) {
8 removeWaiter(q);
9 throw new InterruptedException();
10 }
11
12 int s = state;
13 if (s > COMPLETING) {
14 if (q != null)
15 q.thread = null;
16 return s;
17 }
18 else if (s == COMPLETING) // cannot time out yet
19 Thread.yield();
20 else if (q == null)
21 q = new WaitNode();
22 else if (!queued)
23 queued = UNSAFE.compareAndSwapObject(this, waitersOffset,
24 q.next = waiters, q);
25 else if (timed) {
26 nanos = deadline - System.nanoTime();
27 if (nanos <= 0L) {
28 removeWaiter(q);
29 return state;
30 }
31 LockSupport.parkNanos(this, nanos);
32 }
33 else
34 LockSupport.park(this);
35 }
36 }

学习JDK之“Future机制==>多线程”的更多相关文章

  1. Java多线程学习(一)Java多线程入门

    转载请备注地址:https://blog.csdn.net/qq_34337272/article/details/79640870 系列文章传送门: Java多线程学习(一)Java多线程入门 Ja ...

  2. 深度剖析java中JDK动态代理机制

    https://www.jb51.net/article/110342.htm 本篇文章主要介绍了深度剖析java中JDK动态代理机制 ,动态代理避免了开发人员编写各个繁锁的静态代理类,只需简单地指定 ...

  3. dubbo学习之路-SPI机制

    dubbo学习之路-SPI机制 1.SPI 1.1Java SPI 原理 SPI是service provider interface简称.在java JDK中 内置的一种服务提供发现机制.它解决在一 ...

  4. 九、Android学习第八天——广播机制与WIFI网络操作(转)

    (转自:http://wenku.baidu.com/view/af39b3164431b90d6c85c72f.html) 九.Android学习第八天——广播机制与WIFI网络操作 今天熟悉了An ...

  5. 深度剖析JDK动态代理机制

    摘要 相比于静态代理,动态代理避免了开发人员编写各个繁锁的静态代理类,只需简单地指定一组接口及目标类对象就能动态的获得代理对象. 代理模式 使用代理模式必须要让代理类和目标类实现相同的接口,客户端通过 ...

  6. java学习笔记09--反射机制

    java学习笔记09--反射机制 什么是反射: 反射是java语言的一个特性,它允许程序在运行时来进行自我检查并且对内部的成员进行操作.例如它允许一个java的类获取他所有的成员变量和方法并且显示出来 ...

  7. Storm学习笔记 - 消息容错机制

    Storm学习笔记 - 消息容错机制 文章来自「随笔」 http://jsynk.cn/blog/articles/153.html 1. Storm消息容错机制概念 一个提供了可靠的处理机制的spo ...

  8. 异常处理器详解 Java多线程异常处理机制 多线程中篇(四)

    在Thread中有异常处理器相关的方法 在ThreadGroup中也有相关的异常处理方法 示例 未检查异常 对于未检查异常,将会直接宕掉,主线程则继续运行,程序会继续运行 在主线程中能不能捕获呢? 我 ...

  9. 利用JDK动态代理机制实现简单拦截器

    利用JDK动态代理机制实现简单的多层拦截器 首先JDK动态代理是基于接口实现的,所以我们先定义一个接口 public interface Executer { public Object execut ...

随机推荐

  1. yapi安装 - windows

    yapi官网https://hellosean1025.github.io/yapi/devops/index.html 简介 ​ YApi 是高效.易用.功能强大的 api 管理平台,旨在为开发.产 ...

  2. Oracle的发展历程

    我们学习的是ORACLE(甲骨文)公司(就是收购Sun公司的甲骨文公司)的Oracle数据库(Oracle Database).Oracle数据库是关系型数据库中的大型数据库,存储量大,而且也非常安全 ...

  3. weblogic补丁安装失败

    转至:https://www.cnblogs.com/lsdb/p/7234989.html weblogic补丁安装失败(Patch B25A is mutually exclusive and c ...

  4. Pycharm:注释、删除所有注释

    1.# 单行注释 2. ''' 多行注释 ''' 3.删除所有注释 CTRL+R进入替换模式,勾选右上角正则表达式,然后在搜索框输入  #.*,Replace All即可

  5. Python之简单的用户名密码验证

    题目要求: 输入用户名密码 认证成功后显示欢迎信息 输错三次后锁定   #要求使用文件存储用户名和密码,每次从文件读入用户名和密码用来验证,如果输错三次密码该账户会被锁定,并讲锁定的用户名写入文件 # ...

  6. 2022最新IntellJ IDEA的mall开发部署文档

    目录 版本说明 一.概述 二.基本构建 三.Git 导入编译器 四.模块描述浅析 五.配置文档 application.yml修改,涉及模块 application-dev.yml修改,涉及模块 ge ...

  7. kafka 第一次小整理(草稿篇)————整理一下自己的认知

    前言 简单整理一些自己使用kafka的一些感受. 正文 一切都要回到真实的世界上, 计算机世界只是真实事件的一个缩影. 计算机世界有一个重要的东西,那就是数据库. 数据库记录着真实世界发生了什么,准确 ...

  8. CVE-2017-7269(IIS远程代码执行)

    利用CVE-2017-7269让IIS向自己的msf反弹一个shell 漏洞编号:CVE2017-7269 服务器版本:Windows server 2003 中间件:IIS6.0 攻击工具:meta ...

  9. 基于long pull实现简易的消息系统参考

    我们都用过消息中间件,它的作用自不必多说.但对于消费者却一直有一些权衡,就是使用push,还是pull模式的问题,这当然是各有优劣.当然,这并不是本文想讨论的问题.我们想在不使用长连接的情意下,如何实 ...

  10. go语言学习入门篇 2--轻量级线程的实现

    很多有过 JVM 相关语言工作经验的程序员或许都遇到过如下问题: 超出 thread 限制导致内存溢出.在作者的笔记本的 linux 上运行,这种情况一般发生在创建了 11500 个左右的 threa ...