学习JDK之“Future机制==>多线程”
什么是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机制==>多线程”的更多相关文章
- Java多线程学习(一)Java多线程入门
转载请备注地址:https://blog.csdn.net/qq_34337272/article/details/79640870 系列文章传送门: Java多线程学习(一)Java多线程入门 Ja ...
- 深度剖析java中JDK动态代理机制
https://www.jb51.net/article/110342.htm 本篇文章主要介绍了深度剖析java中JDK动态代理机制 ,动态代理避免了开发人员编写各个繁锁的静态代理类,只需简单地指定 ...
- dubbo学习之路-SPI机制
dubbo学习之路-SPI机制 1.SPI 1.1Java SPI 原理 SPI是service provider interface简称.在java JDK中 内置的一种服务提供发现机制.它解决在一 ...
- 九、Android学习第八天——广播机制与WIFI网络操作(转)
(转自:http://wenku.baidu.com/view/af39b3164431b90d6c85c72f.html) 九.Android学习第八天——广播机制与WIFI网络操作 今天熟悉了An ...
- 深度剖析JDK动态代理机制
摘要 相比于静态代理,动态代理避免了开发人员编写各个繁锁的静态代理类,只需简单地指定一组接口及目标类对象就能动态的获得代理对象. 代理模式 使用代理模式必须要让代理类和目标类实现相同的接口,客户端通过 ...
- java学习笔记09--反射机制
java学习笔记09--反射机制 什么是反射: 反射是java语言的一个特性,它允许程序在运行时来进行自我检查并且对内部的成员进行操作.例如它允许一个java的类获取他所有的成员变量和方法并且显示出来 ...
- Storm学习笔记 - 消息容错机制
Storm学习笔记 - 消息容错机制 文章来自「随笔」 http://jsynk.cn/blog/articles/153.html 1. Storm消息容错机制概念 一个提供了可靠的处理机制的spo ...
- 异常处理器详解 Java多线程异常处理机制 多线程中篇(四)
在Thread中有异常处理器相关的方法 在ThreadGroup中也有相关的异常处理方法 示例 未检查异常 对于未检查异常,将会直接宕掉,主线程则继续运行,程序会继续运行 在主线程中能不能捕获呢? 我 ...
- 利用JDK动态代理机制实现简单拦截器
利用JDK动态代理机制实现简单的多层拦截器 首先JDK动态代理是基于接口实现的,所以我们先定义一个接口 public interface Executer { public Object execut ...
随机推荐
- 【windows 访问控制】开篇、访问控制模型模型
访问控制模型的各个部分 访问控制模型有两个基本部分: 访问令牌,其中包含有关已登录用户的信息 安全描述符,其中包含用于保护安全对象 的安全信息 用户登录时 ,系统会对用户 的帐户名和密码进行身份验证. ...
- C# pdb类型文件的作用之一:记录具体异常的关键信息,如文件路径和行号
pdb 是 Program Debug Database 的简称: 背景 我负责的一个Services(服务)出问题了,原因是一个 dll 内部逻辑出问题了: 在本地修改源码后,重新生成dll(Deb ...
- C#早期绑定&后期绑定
早期绑定(early binding),又可以称为静态绑定(static binding).在使用某个程序集前,已知该程序集里封装的方法.属性等.则创建该类的实例后可以直接调用类中封装的方法. 后期绑 ...
- 图解CPU缓存一致性问题
产生背景 CPU的读取速度比内存的快,一个快一个慢,就会有矛盾,就会有人想要解决这个矛盾,所以就提出多级缓存来解决,如下图所示. L1级缓存:分为数据域和程序域. L2级缓存:二级缓存. L3级缓存 ...
- Winform实现客户端的自动更新
话不多说,直接上干货.当然也希望各位前辈多多指导. 自动更新客户端的设计原理图 请花两分钟时间,仔细阅读下面这张图,明白客户端自动升级的原理. 自动更新的效果图 1. ...
- spring的依赖注入的四种方式,数组与集合注入;引用注入;内部bean注入
三种注入方式 第一种: 基于构造函数 hi.java (bean) package test_one; public class hi { private String name; public hi ...
- 文件上传漏洞之js验证
0x00 前言 只有前端验证=没有验证 0x01 剔除JS 打开burpsuite,进入Proxy的Options,把Remove all JavaScript选上. 设置浏览器代理直接上传PHP木马 ...
- Go标准的目录结构(自总结)
微服务版 ├── LICENSE.md ├── Makefile //在任何一个项目中都会存在一些需要运行的脚本,这些脚本文件应该被放到 /scripts 目录中并由 Makefile 触发 ├── ...
- cve-2018-12613-PhpMyadmin后台文件包含漏洞
前言 刚开始复现这个漏洞的时候是在自己的本机上然后跟着大佬的复现步骤可是没有预期的结果最后看了另一篇文章 当时整个人都麻了 首先何为phpMyAdmin 根据官方的说明phpMy ...
- libx264开发笔记(一):libx264介绍、海思平台移植编译
前言 在编译ffmpeg时,使用到h264编码时是需要依赖libx264的,本文章是将将libx264作为静态库移植到海思上. 相关博客 <Qt开发笔记之编码x264码流并封装mp4(一 ...