多线程高并发编程(7) -- Future源码分析
一.概念
A Future计算的结果。 提供方法来检查计算是否完成,等待其完成,并检索计算结果。 结果只能在计算完成后使用方法get进行检索,如有必要,阻塞,直到准备就绪。 取消由cancel方法执行。 提供其他方法来确定任务是否正常完成或被取消。 计算完成后,不能取消计算。 如果您想使用Future ,以便不可撤销,但不提供可用的结果,则可以声明Future<?>表格的类型,并返回null作为基础任务的结果。
public interface Future<V> {
//尝试取消执行此任务。如果任务已经完成,已经被取消或由于某些其他原因而无法取消,则此尝试将失败。
//如果成功,并且在调用 cancel 时此任务尚未开始,则该任务永远无法运行。
//如果任务已经开始,则 mayInterruptIfRunning 参数确定是否应中断执行该任务的线程以尝试停止该任务。
//mayInterruptIfRunning == true, 表示中断执行中的线程,false 表示让线程正常完成
boolean cancel(boolean mayInterruptIfRunning);
//如果此任务在正常完成之前被取消,则返回true。
boolean isCancelled();
//如果此任务完成,则返回true。完成可能是由于正常终止,异常或取消引起的,在所有这些情况下,此方法都将返回true。
boolean isDone();
//必要时等待计算完成,然后检索其结果
V get() throws InterruptedException, ExecutionException;
//必要时最多等待给定时间以完成计算,然后检索其结果(如果有)。
V get(long timeout, TimeUnit unit)
throws InterruptedException, ExecutionException, TimeoutException;
}
Future是一个接口,提供了方法来检测当前的任务是否已经结束,还可以等待任务结束并且拿到一个结果,通过调用Future的get()方法可以当任务结束后返回一个结果值,如果工作没有结束,则会阻塞当前线程,直到任务执行完毕;可以通过调用cancel()方法来停止一个任务,如果任务已经停止,则cancel()方法会返回true;如果任务已经完成或者已经停止了或者这个任务无法停止,则cancel()会返回一个false。当一个任务被成功停止后,他无法再次执行。isDone()和isCancel()方法可以判断当前工作是否完成和是否取消。
类图结构:
- ScheduledFuture:这个接口表示一个延时的行为可以被取消。通常一个安排好的future是定时任务SchedualedExecutorService的结果;
- RunnableFuture: 这个接口同时继承Future接口和Runnable接口,在成功执行run()方法后,可以通过Future访问执行结果;
- ForkJoinTask:基于任务的抽象类,可以通过ForkJoinPool来执行。一个ForkJoinTask是类似于线程实体,但是相对于线程实体是轻量级的。大量的任务和子任务会被ForkJoinPool池中的真实线程挂起来,以某些使用限制为代价;
- CompletableFuture:一个Future类是显示的完成,而且能被用作一个完成等级,通过它的完成触发支持的依赖函数和行为。当两个或多个线程要执行完成或取消操作时,只有一个能够成功;
- RunnableScheduledFuture:执行延迟和周期性任务;在成功执行run()方法后,可以通过Future访问执行结果;
- FutureTask:可取消的异步计算
- 该类提供了一个Future的基本实现 ,具有启动和取消计算的方法,查询计算是否完整,并检索计算结果。结果只能在计算完成后才能检索; 如果计算尚未完成,则get方法将阻止。一旦计算完成,则无法重新启动或取消计算(除非使用runAndReset()调用计算 );
- A FutureTask可用于包装Callable或Runnable对象。 因为FutureTask实现Runnable ,一个FutureTask可以提交到一个Executor执行;
- RecursiveTask:递归结果ForkJoinTask;
- RecursiveAction:递归结果ForkJoinTask;
二.用法
一个场景,我们要学习做饭,那么我们需要准备厨具和食材,厨具通过电子商务网购,食材去菜市场挑选。那么可以使用多线程来并发进行,即我们可以先网购下单,在等待快递员送货过来的这段时间去菜市场买食材,节省时间,提高效率。
- 直接开启线程,使用类继承Thread重写方法实现网购,join阻塞直到厨具到达才开始做饭。
public class FutureTest {
public static void main(String[] args) throws InterruptedException {
long startTime = System.currentTimeMillis();
OnlineShopping shopping = new OnlineShopping();
shopping.start();
Thread.sleep(2000);//等待送货执行完
System.out.println("第二步:食材到位");
shopping.join();//阻塞订单直到快递送到得到厨具
System.out.println("第三步:开始厨艺");
System.out.println("总共用时:" + (System.currentTimeMillis() - startTime) + "ms");
} static class OnlineShopping extends Thread {
@Override
public void run() {
System.out.println("第一步:下单");
System.out.println("第一步:等待送货");
try {
Thread.sleep(5000);//送货中
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("第一步:快递送到");
}
}
}
//======结果======
第一步:下单
第一步:等待送货
第二步:食材到位
第一步:快递送到
第三步:开始厨艺
总共用时:5003ms - 使用Future模式来完成上述操作,通过Callable返回结果来获取厨具,可以通过FutureTask灵活地操作订单,由此可见,比继承Thread完成的订单,Future模式更具有灵活性,
public class FutureTest {
public static void main(String[] args) throws Exception {
long startTime = System.currentTimeMillis();
Callable<String> shopping = () ->{
System.out.println("第一步:下单");
System.out.println("第一步:等待送货");
Thread.sleep(5000);//快递员送货中
System.out.println("第一步:快递送到");
return "厨具到达";
};
FutureTask<String> task = new FutureTask<>(shopping);
new Thread(task).start();
Thread.sleep(2000);//保证下单操作执行到“等待送货”中
System.out.println("第二步:食材到位");
if (!task.isDone()) { // 联系快递员,询问是否到货
System.out.println("第三步:厨具还没到,心情好就等着(心情不好就调用cancel方法取消订单)");
}
String chuju = task.get();//得到厨具
System.out.println("第三步:开始厨艺");
System.out.println("总共用时:" + (System.currentTimeMillis() - startTime) + "ms");
}
}
//======结果======
第一步:下单
第一步:等待送货
第二步:食材到位
第三步:厨具还没到,心情好就等着(心情不好就调用cancel方法取消订单)
第一步:快递送到
第三步:开始厨艺
总共用时:5048ms
三.分析
使用Future模式的三部曲:
- 创建Callable重写call方法,把网购逻辑封装到call中,返回定义结果“厨具”;
public interface Callable<V> {
V call() throws Exception;
} - 创建FutureTask,把Callable实例放入FutureTask的构造方法中;
public class FutureTask<V> implements RunnableFuture<V>{
public FutureTask(Callable<V> callable) {
if (callable == null)
throw new NullPointerException();
this.callable = callable;
this.state = NEW; // 确保callable的可见性
}
public FutureTask(Runnable runnable, V result) {
this.callable = Executors.callable(runnable, result);
this.state = NEW; // 确保callable的可见性
}
}
public interface RunnableFuture<V> extends Runnable, Future<V> {
void run();
}FutureTask的run方法:Callable的call是被FutureTask的run方法调用的,不是异步运行的;
public void run() {
// 1. 如果 state != NEW 说明 run 方法已经运行过,直接 return
// 2. 如果 state == NEW && CAS 竞争 设置 runner 失败,说明已经有别的线程在运行,直接 return
// NEW 的状态由构造方法初始化,runner 是运行该 Callable 的线程
if (state != NEW ||
!UNSAFE.compareAndSwapObject(this, runnerOffset,
null, Thread.currentThread()))
return;
try {
Callable<V> c = callable;// 这里的callable是从构造方法里面传人的
if (c != null && state == NEW) {
V result;
boolean ran;// 标识符
try {
result = c.call();//获得结果
ran = true;
} catch (Throwable ex) {//异常
result = null;
ran = false;
setException(ex);
}
if (ran)//成功没有异常,设置返回值
set(result);
}
} finally {
// runner must be non-null until state is settled to
// prevent concurrent calls to run()
//在状态设置之前,runner必须是非空的,以防止对run()的并发调用
runner = null;
// state must be re-read after nulling runner to prevent
// leaked interrupts
//为防止泄漏中断,必须在空runner之后将状态设置为重复读
int s = state;
// 如果最终状态 >= INTERRUPTING,则处理中断
// cancel 方法会通过参数 mayInterruptIfRunning 来设置 state 的值
if (s >= INTERRUPTING)
handlePossibleCancellationInterrupt(s);
}
}状态属性state:
private volatile int state;//状态,volatile让状态可见性
private static final int NEW = 0;//构造方法创建时的状态
private static final int COMPLETING = 1;//这是一个中间态,完成时和出现异常时有使用到
private static final int NORMAL = 2;//完成运行时的最终状态
private static final int EXCEPTIONAL = 3;//异常时的最终状态
private static final int CANCELLED = 4;//已取消
private static final int INTERRUPTING = 5;//中断中
private static final int INTERRUPTED = 6;//已中断 可能的 state 转换:
NEW -> COMPLETING -> NORMAL
NEW -> COMPLETING -> EXCEPTIONAL
NEW -> CANCELLED
NEW -> INTERRUPTING -> INTERRUPTEDset设置返回值:
private Object outcome;//通过get方法获得的返回值
//设置返回值,状态NEW -> COMPLETING -> NORMAL
protected void set(V v) {
// 这里为什么要用 CAS 因为可能会和 cancel 方法产生竞争。
// 如果竞争失败,说明取消竞争成功,在 cancel 方法承担唤醒的工作,所以直接跳过。
if (UNSAFE.compareAndSwapInt(this, stateOffset, NEW, COMPLETING)) {//NEW -> COMPLETING
// 竞争成功
outcome = v;//outcome为返回结果
UNSAFE.putOrderedInt(this, stateOffset, NORMAL); // 最终状态为NORMAL,COMPLETING -> NORMAL
finishCompletion();
}
}setException运行时异常:
//状态:NEW -> COMPLETING -> EXCEPTIONAL
protected void setException(Throwable t) {
// 这里为什么要用 CAS 因为可能会和 cancel 方法产生竞争。
// 如果竞争失败,说明取消竞争成功,在 cancel 方法承担唤醒的工作,所以直接跳过。
if (UNSAFE.compareAndSwapInt(this, stateOffset, NEW, COMPLETING)) {//NEW -> COMPLETING
// 竞争成功
outcome = t; // outcome 为一个 Throwable
// 把最终状态改为 EXCEPTIONAL
UNSAFE.putOrderedInt(this, stateOffset, EXCEPTIONAL); // final state,COMPLETING -> EXCEPTIONAL
finishCompletion();
}
}finishCompletion:
//删除当前线程并唤醒所有等待线程,调用done(),并取消进行中的方法
private void finishCompletion() {
// assert state > COMPLETING;
//从 waiters 末尾开始遍历,for 自旋直到 CAS 成功。
for (WaitNode q; (q = waiters) != null;) {
// 使用 CAS 把 waiters 设置为 null,和 awaitDone 和 removeWatier 方法竞争
if (UNSAFE.compareAndSwapObject(this, waitersOffset, q, null)) {
// 自旋唤醒所有线程
for (;;) {
Thread t = q.thread;
if (t != null) {
q.thread = null;
LockSupport.unpark(t);// 唤醒线程
}
WaitNode next = q.next;
if (next == null)
break;
q.next = null; // unlink to help gc
q = next;
}
break;
}
}
// 这个一个空方法,主要用于继承重写,这也是模板模式的体现
done();
callable = null; // to reduce footprint
}
protected void done() { } //===========例子==============
//ExecutorCompletionService 的作用就是把线程池的执行结果放到一个已完成队列中,方便获取执行结果,其内部主要通过一个 FutureTask 的实现类 QueueingFuture 来实现这个功能:
private class QueueingFuture extends FutureTask<Void> {
QueueingFuture(RunnableFuture<V> task) {
super(task, null);
this.task = task;
}
protected void done() { completionQueue.add(task); }//done方法是FutureTask方法的重写。FutureTask在完成时会执行done方法,把task放入已完成队列completionQueue。
private final Future<V> task;
}get获得返回结果:
public V get() throws InterruptedException, ExecutionException {
int s = state;//得到状态
if (s <= COMPLETING)//状态未完成,把获取结果的线程放入等待链表,然后阻塞,直至被中断、完成或出现异常。
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 (;;) {//自旋
//如果已经被中断,则removeWaiter,抛出中断异常
if (Thread.interrupted()) {
removeWaiter(q);
throw new InterruptedException();
}
int s = state;
if (s > COMPLETING) {//task已经结束
if (q != null)
q.thread = null;
return s;
}
//第二个线程进来,正在运行,发现前面有等待节点,则让出cpu
else if (s == COMPLETING) // cannot time out yet
Thread.yield();
// 第一次遍历,初始化 WaitNode,第一个节点进来时进行,第一个线程状态是new
else if (q == null)
q = new WaitNode();
// 是否已入队,没有则把WaitNode接到末尾,第一个线程第二次遍历时运行下面代码
else if (!queued)
// 和 finishCompletion 和 removeWaiter 竞争
// 1. finishCompletion竞争成功,说明state已经 > COMPLETING则下次循环就会退出
// 2. removeWaiter竞争成功,说明waiters变化了,下一次循环再次竞争
queued = UNSAFE.compareAndSwapObject(this, waitersOffset,
q.next = waiters, q);
// 如果使用了计时,则判断是否超时,如果超时则移出WaitNode并立即返回无需等待结果,否则阻塞 nanos
else if (timed) {
nanos = deadline - System.nanoTime();
if (nanos <= 0L) {
removeWaiter(q);
return state;
}
LockSupport.parkNanos(this, nanos);
}
else
//阻塞,直到被唤醒(正常完成 || 异常 || 中断)
LockSupport.park(this);
}
}
//根据awaitDone返回状态返回结果或抛出异常
private V report(int s) throws ExecutionException {
Object x = outcome;
if (s == NORMAL)//正常
return (V)x;
if (s >= CANCELLED)//取消
throw new CancellationException();
// task 执行过程中出现异常
throw new ExecutionException((Throwable)x);
}removeWaiter:
/**
* Tries to unlink a timed-out or interrupted wait node to avoid
* accumulating garbage. Internal nodes are simply unspliced
* without CAS since it is harmless if they are traversed anyway
* by releasers. To avoid effects of unsplicing from already
* removed nodes, the list is retraversed in case of an apparent
* race. This is slow when there are a lot of nodes, but we don't
* expect lists to be long enough to outweigh higher-overhead
* schemes.
*尝试取消链接超时或中断的等待节点以避免堆积垃圾。内部节点的拼接没有CAS,
*因为这对释放者无论如何遍历都没有影响。 为了避免已删除节点节点未拼接的影响,
*如果出现明显的竞争,则重新遍历列表。 当节点很多时会很慢,但是我们不
*期望列表足够长以抵消较高的开销计划。
*/
private void removeWaiter(WaitNode node) {
if (node != null) {
node.thread = null;
retry:
for (;;) { // restart on removeWaiter race
//遍历整个链表
for (WaitNode pred = null, q = waiters, s; q != null; q = s) {
s = q.next;
//把q当作前一个节点,遍历下一个节点
if (q.thread != null)
pred = q;
// q.thread == null && pred != null,表示当前节点不是第一个节点,是一个中间节点
// 这里没有使用 CAS,如果出现多个线程同时遍历,前一个节点变为null,则重新从头遍历
// 为什么没有使用 CAS 因为作者的想法是这个链表不会太长,所以我们使用时不应该使这个链表太长
// 操作:把下一个节点连接到前一个节点的后面
else if (pred != null) {
pred.next = s;//把s连接到pred后面
if (pred.thread == null) // check for race
continue retry;
}
// q.thread == null && pred == null,表示第一个节点的 thread == null,
// 这里使用 CAS,因为可能多个线程在操作
// 操作:把下一个节点设置为末尾节点,如果竞争失败则重新从头遍历
else if (!UNSAFE.compareAndSwapObject(this, waitersOffset,
q, s))
continue retry;
}
break;
}
}
}isDone:
public boolean isDone() {
return state != NEW;
} 创建Thread,把FutureTask实例放入构造方法中,start开启线程
参考:https://www.jianshu.com/p/414cc2f0002c
多线程高并发编程(7) -- Future源码分析的更多相关文章
- 多线程高并发编程(3) -- ReentrantLock源码分析AQS
背景: AbstractQueuedSynchronizer(AQS) public abstract class AbstractQueuedSynchronizer extends Abstrac ...
- 多线程高并发编程(10) -- ConcurrentHashMap源码分析
一.背景 前文讲了HashMap的源码分析,从中可以看到下面的问题: HashMap的put/remove方法不是线程安全的,如果在多线程并发环境下,使用synchronized进行加锁,会导致效率低 ...
- 多线程高并发编程(8) -- Fork/Join源码分析
一.概念 Fork/Join就是将一个大任务分解(fork)成许多个独立的小任务,然后多线程并行去处理这些小任务,每个小任务处理完得到结果再进行合并(join)得到最终的结果. 流程:任务继承Recu ...
- Java 多线程高并发编程 笔记(一)
本篇文章主要是总结Java多线程/高并发编程的知识点,由浅入深,仅作自己的学习笔记,部分侵删. 一 . 基础知识点 1. 进程于线程的概念 2.线程创建的两种方式 注:public void run( ...
- Java高并发程序设计学习笔记(五):JDK并发包(各种同步控制工具的使用、并发容器及典型源码分析(Hashmap等))
转自:https://blog.csdn.net/dataiyangu/article/details/86491786#2__696 1. 各种同步控制工具的使用1.1. ReentrantLock ...
- Java并发系列[5]----ReentrantLock源码分析
在Java5.0之前,协调对共享对象的访问可以使用的机制只有synchronized和volatile.我们知道synchronized关键字实现了内置锁,而volatile关键字保证了多线程的内存可 ...
- Java并发系列[2]----AbstractQueuedSynchronizer源码分析之独占模式
在上一篇<Java并发系列[1]----AbstractQueuedSynchronizer源码分析之概要分析>中我们介绍了AbstractQueuedSynchronizer基本的一些概 ...
- Java并发系列[3]----AbstractQueuedSynchronizer源码分析之共享模式
通过上一篇的分析,我们知道了独占模式获取锁有三种方式,分别是不响应线程中断获取,响应线程中断获取,设置超时时间获取.在共享模式下获取锁的方式也是这三种,而且基本上都是大同小异,我们搞清楚了一种就能很快 ...
- 多线程与高并发(三)—— 源码解析 AQS 原理
一.前言 AQS 是一个同步框架,关于同步在操作系统(一)-- 进程同步 中对进程同步做了些概念性的介绍,我们了解到进程(线程同理,本文基于 JVM 讲解,故下文只称线程)同步的工具有很多:Mutex ...
随机推荐
- 操作文件-取出一个60s内log日志中ip访问次数超过100次的ip
import timea=0while True: d={} f = open(r"/Users/**juan/Downloads/access.log",encoding=&qu ...
- 中阶 d04 xml 概念及使用
idea新建xml文件https://www.jianshu.com/p/b8aeadae39b0 或https://blog.csdn.net/Hi_Boy_/article/details/804 ...
- 二、Python2.7的安装并与Python3.8共存
一:Python解释器为什么要2个版本? 众所周知,Python2.7是一个过渡版本. 很多公司写的项目并不是基于最新的Python3写的,在之后进行一些项目更改的时候,Python3的语法有一些并不 ...
- 使用基于vuecli创建的目录推送到指定远程分支
笔者使用vuecli创建项目目录以后,在想将该目录提交到远程仓库时发现行不通,在忙活了一下午以后写下此文 1.github上新建一空分支,然后克隆该分支地址: https://github.com/ ...
- 基于my-DAQ的温室迷你温室设计
这是一个小项目,采用NI的my-DAQ做数据采集,需要采集的数据有温度(LM35),气体(MQ2),需要控制的设备有风扇.加热棒,另外还有光照亮度调节. 一.数据采集 1.LM35 LM35是模拟输出 ...
- Application.Exit
Application.Exit:通知winform消息循环退出.Environment.Exit:终止当前进程,返回exitcode给操作系统 Application.Exit会在所有前台线程退出后 ...
- 基于RabbitMQ的Rpc框架
参考文档:https://www.cnblogs.com/ericli-ericli/p/5917018.html 参考文档:RabbitMQ 实现RPC MQ的使用场景大概包括解耦,提高峰值处理能力 ...
- C - Sweets Eating
规律题 前缀和+规律 先求前缀和...答案为c[i]=arr[i]+c[i-m]//i>m时. #include<bits/stdc++.h> using namespace std ...
- Android | 教你如何在安卓上实现通用卡证识别,一键各种卡绑定
目录 前言 通用卡证识别的应用场景 如何使用通用卡证识别服务 集成通用卡证识别服务的关键流程 开发实战 1 开发准备 1.1 在项目级gradle里添加华为maven仓 1.2 在应用级的build. ...
- Numpy学习-(2)
我学习numpy过程的记录 1. 切片和索引 (1) 两种切片方式示例: (2) 多维数组: import numpy as np a = np.array([[1,2,3],[3,4,5],[4,5 ...