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. 【hyperscan】hyperscan开源了!

    hyperscan开源了! 官网:https://01.org/zh/hyperscan 1. 新闻背景 当地时间10月19日,intel将它的高速正则表达式匹配引擎hyperscan开源了,版本4. ...

  2. python 数据库连接及操作

    Python DB-API使用流程: 引入API模块. 获取与数据库的连接. 执行SQL语句和存储过程. 关闭数据库连接. def mysql_dbtest(): config = { 'host': ...

  3. Django自带的后台管理样式找不到的问题。

    今天发现自己用uwsgi,nginx部署完服务器后,又想用自带的Django服务器进行后台管理调试,发现Django后代管理页面样式找不到.又查看了路径发现是正确的.网上看了很多方法.最后才发现自己把 ...

  4. gensim与numpy array 互转

    目的 将gensim输出的格式转化为numpy array格式,支持作为scikit-learn,tensorflow的输入 实施 使用nltk库的停用词和网上收集的资料整合成一份新的停用词表,用来过 ...

  5. swiper4-vue 不使用loop,由最后一张跳到第一张

    <template> <div class="swiper-box"> <div class="swiper-container" ...

  6. odoo开发笔记 -- 用户配置界面如何增加模块访问权限

    在odoo设置界面,点击用户,进入用户配置界面,会看到: 访问权 | 个人资料菜单 在访问权 page菜单界面,可以看到系统预制的一些模块都会显示在这里, 那么,我们自己开发的模块如何显示在这块呢,从 ...

  7. 课程一(Neural Networks and Deep Learning)总结——1、Logistic Regression

    ---------------------------------------------------------------------------------------------------- ...

  8. 修复/lib/ld-linux.so.2: bad ELF interpreter: No such file or directory问题

    在配置MongDB的是时候出现这/lib/ld-linux.so.2问题 [root@localhost local]# /usr/local/mongodb/mongodb/bin/mongod - ...

  9. .net core2 mvc项目中,加入RazorPages页面

    2017.08.22 试验结果: 1.手工添加/Pages文件夹 2.复制/Views/_ViewImports.cshtml到/Pages/_ViewImports.cshtml  2.1 修改@u ...

  10. 纯CSS3手风琴图片滑动特效

    要求 必备知识 基本了解CSS语法,初步了解CSS3语法知识. 开发环境 Adobe Dreamweaver CS6/Chrome浏览器 演示地址 演示地址 制作CSS3制作手风琴图片滑动效果,我们仅 ...