Future

当向一个ExecutorService提交任务后可以获得一个Future对象,在该对象上可以调用getcancel等命令来获取任务运行值或者是取消任务。下面是一个简单的计数任务:

public class NormalFuture {

    public static void main(String[] args) throws Exception {
ExecutorService executorService = Executors.newSingleThreadExecutor(); Future<Integer> count = executorService.submit(new Counting(2)); System.out.println("total:" + count.get());
}
} class Counting implements Callable<Integer> {
private final long period; public Counting(long period) {
this.period = TimeUnit.SECONDS.toNanos(period);
} public Integer call() throws Exception {
long deadline = System.nanoTime() + period;
int count = 0; for (;;) { if (deadline <= System.nanoTime()) {
break;
} System.out.println(++count); TimeUnit.MILLISECONDS.sleep(100);
}
return count;
}
}

Future 的用途

future主要用于与之关联的任务的状态、结果并可取消该任务。

异步任务

future用在需要异步执行的任务上,即不需要立即获取结果或者说无法立即获得结果,但在这段时间内主线程还可以做一些其他的工作,等到这些工作做完确实到了没有任务结果不能进行下去的地步时,可以用get调用来获取任务结果。

同步任务

一般来说任何异步执行的框架总是可以转换成同步执行的,只要一直等结果就行了。当向ExecutorService提交任务后,就可以立马调用Future对象的get方法,就跟直接在当前线程内调用方法然后等待一样。如果没有Future机制那么任务提交到线程池后我们就无法知晓任务的状态。当然如果没有JDK自带的Future机制,我们可以在提交的Runnable或者Callable对象内实现相应的一些线程通知同步等方法(如消息队列,信号量)。不过既然JDK已经提供了如Future, ExecutorCompletionService这些机制,再这么做就有些重复造轮的感觉了。

任务取消

任务并不是直接将线程池内运行该任务的线程停止(Thread类里相关的方法早已弃用)。而是取消其他线程在该任务对应的Future.get上的等待,也可以选择对运行任务的线程设置interrupt,如果任务线程在可中断的操作上那么线程池内执行该任务的线程会马上退出。但如果该任务是一个计算密集型的任务(没有包含任何可中断的方法调用)那么该线程还会继续执行,不过所有在与该线程关联的Future对象上等待的线程已经被激活并收到任务已取消的异常。

mayInterruptIfRunning = false

取消future对应的任务时可以使用mayInterruptIfRunning = false,那么这个取消过程只会使等待在上面的线程收到CancellationException,实际运行任务的线程还是会继续进行。如下面的例子所示:

public class NormalFuture {

    public static void main(String[] args) throws Exception {
ExecutorService executorService = Executors.newSingleThreadExecutor(); Future<Integer> count = executorService.submit(new Counting(5)); TimeUnit.MILLISECONDS.sleep(500);
count.cancel(false); // console counting output will continue with argument mayInterruptIfRunning = false System.out.println("total:" + count.get()); }
}

Counting任务中的输出在cancel执行后依然会继续,但主线程在count变量上get的时候确实收到了异常:

1
2
3
4
5
Exception in thread "main" java.util.concurrent.CancellationException
at java.util.concurrent.FutureTask.report(FutureTask.java:121)
at java.util.concurrent.FutureTask.get(FutureTask.java:188)
at futures.NormalFuture.main(NormalFuture.java:18)
6
7
8
9
10
...

mayInterruptIfRunning = true

当我们在取消任务时使用了mayInterruptIfRunning = true,那么将会向执行任务的线程进行一个interrupt操作,如果里面的任务能够响应这个请求(可中断的方法调用如sleep),线程一般来说会退出,任务会结束。下面把取消任务的参数改为true

       count.cancel(true);

再运行程序,可以看到Counting任务在主线程执行cancel调用后马上就停止了(没有继续输出数字)

1
2
3
4
5
Exception in thread "main" java.util.concurrent.CancellationException
at java.util.concurrent.FutureTask.report(FutureTask.java:121)
at java.util.concurrent.FutureTask.get(FutureTask.java:188)
at futures.NormalFuture.main(NormalFuture.java:18)

不可中断任务

什么是不可中断任务,就是其中没有方法对线程的interrupt状态进行检测并在interrupt置位时能够主动抛出异常或者退出。下面几个就是不可中断任务:

    for (int i=0; i<10000000; i++) {
System.out.println(i);
}

上面的纯粹的在进行计算和输出(println是不可中断调用)

再比如

    for (int i=0; i<1000000; i++) {
try {
TimeUnit.SECONDS.sleep(1);
} catch (Throwable ignore) {
}
}

虽然sleep调用是可中断方法,但是外层直接捕获了异常并忽略,这样不会使线程退出,也就变得不可中断了。

class Counting implements Callable<Integer> {
private final long period; public Counting(long period) {
this.period = TimeUnit.SECONDS.toNanos(period);
} public Integer call() throws Exception {
long deadline = System.nanoTime() + period;
int count = 0; for (;;) { if (deadline <= System.nanoTime()) {
break;
} System.out.println(++count); try {
TimeUnit.MILLISECONDS.sleep(100);
} catch (InterruptedException e) {
System.out.println("known interrupted, but continue.");
}
}
return count;
}
}

上面就把Counting任务改为了一个不可中断的任务,此时即使是用Future.cancel(true)去取消,任务还是会照样执行,运行输出如下:

1
2
3
4
5
Exception in thread "main" java.util.concurrent.CancellationException
at java.util.concurrent.FutureTask.report(FutureTask.java:121)
at java.util.concurrent.FutureTask.get(FutureTask.java:188)
at futures.NormalFuture.main(NormalFuture.java:18)
known interrupted, but continue.
6
7
8
9
10

FutureTask

Future只是一个接口,具体使用到得实现类为FutureTask它不但实现了Future接口也实现了Runnable接口,这两者的关系非常紧密。简单的想象一下,比如在执行包裹的runnable对象的run方法后修改Future对象中的状态,通知等待在Future.get上的线程。

状态

FutureTask有几个状态

    /**
* The run state of this task, initially NEW. The run state
* transitions to a terminal state only in methods set,
* setException, and cancel. During completion, state may take on
* transient values of COMPLETING (while outcome is being set) or
* INTERRUPTING (only while interrupting the runner to satisfy a
* cancel(true)). Transitions from these intermediate to final
* states use cheaper ordered/lazy writes because values are unique
* and cannot be further modified.
*
* Possible state transitions:
* NEW -> COMPLETING -> NORMAL
* NEW -> COMPLETING -> EXCEPTIONAL
* NEW -> CANCELLED
* NEW -> INTERRUPTING -> INTERRUPTED
*/
private volatile int state;
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;
  • NEW表示刚提交任务到任务计算结束,要设置结果值前的这段时间的状态。也就是说任务运行时其状态也是NEW

    所以可以看到对Future接口中isDone的实现如下:
 public boolean isDone() {
return state != NEW;
}

即凡是不处与NEW的状态都认为任务已经执行完成(当然可能是取消了,或者是抛异常了),这里的完成只是说任务(提交的Runnable或者Callable中的任务函数)运行已经停止了。

  • COMPLETING 表示正在设置任务返回值或者异常值(catch语句中捕获的)

状态转换路径

NEW -> COMPLETING -> NORMAL

正常情况下的转换路径

    protected void set(V v) {
if (UNSAFE.compareAndSwapInt(this, stateOffset, NEW, COMPLETING)) {
outcome = v;
UNSAFE.putOrderedInt(this, stateOffset, NORMAL); // final state
finishCompletion();
}
}
NEW -> COMPLETING -> EXCEPTIONAL

任务抛出异常情况下的转换路径

    protected void setException(Throwable t) {
if (UNSAFE.compareAndSwapInt(this, stateOffset, NEW, COMPLETING)) {
outcome = t;
UNSAFE.putOrderedInt(this, stateOffset, EXCEPTIONAL); // final state
finishCompletion();
}
}
NEW -> CANCELLED

任务取消时的转换路径之一,这里对应Future.cancel(bool)的参数是false时的情况

NEW -> INTERRUPTING -> INTERRUPTED

任务取消时的转换路径之二,这里对应Future.cancel(bool)的参数是true时的情况,即会尝试着向线程池中的执行线程发出interrupt请求,这里不管最终目标线程有没有忽略这个interrupt请求,Future中的状态都会变为INTERRUPTED

执行函数

可以看到在传入的Callable周围包裹了进行状态检测和转换的逻辑,也可以看到对任务异常的处理。

    public void run() {
if (state != NEW || !UNSAFE.compareAndSwapObject(this, runnerOffset, null, Thread.currentThread()))
return;
try {
Callable<V> c = 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 {
....
}
}

Future.get等待队列

当有多个其他线程同时调用Future.get并等待,在任务完成后需要将他们一一唤醒。要实现这个功能可以使用传统的wait&notifyAll或者基于AQS如信号量等同步机制,不过这里使用CAS操作实现了一个无锁队列,确切的说是一个栈。根据栈的特点,它是FILO的,所以最早调用Future.get的线程反而会最晚被唤醒。唤醒与睡眠使用了LockSupport的park系列函数。下面是唤醒的一个实现:

    /**
* Removes and signals all waiting threads, invokes done(), and
* nulls out callable.
*/
private void finishCompletion() {
// assert state > COMPLETING;
for (WaitNode q; (q = waiters) != null;) {
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
}

Future.get等待过程

上述的get等待队列唤醒过程是get上的最后一步。这个完整过程如下,即

  • 当前get等待的线程是否被interrupt请求,如果是,则放弃等待并抛出InterruptedException,可见get方法是一个可中断方法
  • 如果任务已经完成执行则返回任务状态停止循环检查
  • 当前线程对应的等待节点是否已经创建,没有的话进行创建
  • 当前线程对应的等待节点是否已经入队,如果没有则加入Future的等待队列(无锁stack)中
  • 如果以上都不满足则投入睡眠等待唤醒,对于带超时参数的get版本要判断当前是否已经超时
    private int awaitDone(boolean timed, long nanos)
throws InterruptedException {
final long deadline = timed ? System.nanoTime() + nanos : 0L;
WaitNode q = null;
boolean queued = false;
for (;;) {
if (Thread.interrupted()) {
removeWaiter(q);
throw new InterruptedException();
} int s = state;
if (s > COMPLETING) {
if (q != null)
q.thread = null;
return s;
}
else if (s == COMPLETING) // cannot time out yet
Thread.yield();
else if (q == null)
q = new WaitNode();
else if (!queued)
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
LockSupport.park(this);
}
}

最后get函数调用report函数进行结果返回或者抛出异常

    private V report(int s) throws ExecutionException {
Object x = outcome;
if (s == NORMAL)
return (V)x;
if (s >= CANCELLED)
throw new CancellationException();
throw new ExecutionException((Throwable)x);
} public V get() throws InterruptedException, ExecutionException {
int s = state;
if (s <= COMPLETING)
s = awaitDone(false, 0L);
return report(s);
}

钩子函数

在FutureTask中有个空函数protected void done(),子类可以覆盖它以便当任务完成时可以被调用,ExecutorCompletionService类就用到了这个。它有个QueueingFuture继承了FutureTask,QueueingFuture的done函数就是把当前QueueingFuture包裹的内部FutureTask加入到完成队列中,这个有点绕。见代码:

    private class QueueingFuture extends FutureTask<Void> {
QueueingFuture(RunnableFuture<V> task) {
super(task, null);
this.task = task;
}
protected void done() { completionQueue.add(task); }
private final Future<V> task;
}

向一个线程池提交QueueingFuture得到的Future,但在这个Future对象上并不能得到任务结果(super(task, null)),只能等待任务完成。要获取得到任务结果必须从completionQueue队列中获取future对象并在其上调用get函数才行。

Java 并发:Future FutureTask的更多相关文章

  1. java callable future futuretask

    Runnbale封装一个异步运行的任务,可以把它想象成一个没有任何参数和返回值的异步方法.Callable和Runnable相似,但是它有返回值.Callable接口是参数化的类型,只有一个方法cal ...

  2. Java并发编程:Callable、Future和FutureTask

    作者:海子 出处:http://www.cnblogs.com/dolphin0520/ 本博客中未标明转载的文章归作者海子和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置 ...

  3. java并发:获取线程执行结果(Callable、Future、FutureTask)

    初识Callable and Future 在编码时,我们可以通过继承Thread或是实现Runnable接口来创建线程,但是这两种方式都存在一个缺陷:在执行完任务之后无法获取执行结果.如果需要获取执 ...

  4. Java并发:Callable、Future和FutureTask

    Java并发编程:Callable.Future和FutureTask 在前面的文章中我们讲述了创建线程的2种方式,一种是直接继承Thread,另外一种就是实现Runnable接口. 这2种方式都有一 ...

  5. (转)Java并发编程:Callable、Future和FutureTask

    Java并发编程:Callable.Future和FutureTask 在前面的文章中我们讲述了创建线程的2种方式,一种是直接继承Thread,另外一种就是实现Runnable接口. 这2种方式都有一 ...

  6. Java并发编程:Callable、Future和FutureTask(转)

    Java并发编程:Callable.Future和FutureTask 在前面的文章中我们讲述了创建线程的2种方式,一种是直接继承Thread,另外一种就是实现Runnable接口. 这2种方式都有一 ...

  7. Java并发编程原理与实战三十一:Future&FutureTask 浅析

    一.Futrue模式有什么用?------>正所谓技术来源与生活,这里举个栗子.在家里,我们都有煮菜的经验.(如果没有的话,你们还怎样来泡女朋友呢?你懂得).现在女票要你煮四菜一汤,这汤是鸡汤, ...

  8. Java 并发编程——Callable+Future+FutureTask

    Java 并发编程系列文章 Java 并发基础——线程安全性 Java 并发编程——Callable+Future+FutureTask java 并发编程——Thread 源码重新学习 java并发 ...

  9. 15、Java并发编程:Callable、Future和FutureTask

    Java并发编程:Callable.Future和FutureTask 在前面的文章中我们讲述了创建线程的2种方式,一种是直接继承Thread,另外一种就是实现Runnable接口. 这2种方式都有一 ...

  10. 007 Java并发编程:Callable、Future和FutureTask

    原文https://www.cnblogs.com/dolphin0520/p/3949310.html Java并发编程:Callable.Future和FutureTask 在前面的文章中我们讲述 ...

随机推荐

  1. cad.net的undo返回操作

    这是提供给许多从lisp转移到c#的开发人员的一个函数,这个函数利用后绑代码实现undo返回操作. 本代码由edata提供: edata博客 /// <summary> /// 命令动作编 ...

  2. mysql多列索引和最左前缀

    数据库的索引可以加快查询速度,原因是索引使用特定的数据结构(B-Tree)对特定的列额外组织存放,加快存储引擎(索引是存储引擎实现)查找记录的速度.索引优化是数据库优化的最重要手段. 如果查询语句使用 ...

  3. SpringCloud之Ribbon

    一:Ribbon是什么?  Ribbon是Netflix发布的开源项目,主要功能是提供客户端的软件负载均衡算法,将Netflix的中间层服务连接在一起.Ribbon客户端组件提供一系列完善的配置项如连 ...

  4. css经典布局—Sticky footers布局

    参考:http://www.w3cplus.com/CSS3/css-secrets/sticky-footers.html 效果:将footer固定到底部.文章内容不足满屏时 footer在底部,超 ...

  5. flask_mysql入库

    mysql 的入库和MongoDB的有一点点的区别 不过都很重要,都必须要掌握的技能, 现在我来演示一下mysql入库的过程: 首先  我们要导包,这是必不可少的一部分,都不用我说了吧 #导报 imp ...

  6. python传输文件

    传输文件简单版 server端: import socket import struct import json import os share_dir = r'C:\py3Project\路飞\第三 ...

  7. python--使用pickle序列化对象

    pickle序列化对象 如果希望透明地存储 Python 对象,而不丢失其身份和类型等信息,则需要某种形式的对象序列化:它是一个将任意复杂的对象转成对象的文本或二进制表示的过程. 同样,必须能够将对象 ...

  8. 【xsy2274】 平均值 线段树

    题目大意:给你一个长度为$n$的序列$a$,请你求: $\sum\limits_{l=1}^{n}\sum\limits_{r=l}^{n}\dfrac{mex(a_l,a_{l+1},...,a_r ...

  9. jdk1.8一键安装脚本(linux环境)

    1.下载jdk安装包和安装脚本 下载地址:https://pan.baidu.com/s/1bo6ADQ3 其中包括: jdk安装包:jdk-8u151-linux-x64.tar.gz jdk一键安 ...

  10. [每天解决一问题系列 - 0001] Javascript apply和 call对比

    相同点: 每个函数都包含这两个原生的方法 他们两个的效果是一样的,用于在特定的作用域下执行函数,本质上是设置函数内this对象的值. 不同点: 传入的参数类型不同 . apply(函数作用域,arra ...