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. BZOJ 3940--[Usaco2015 Feb]Censoring(AC自动机)

    3940: [Usaco2015 Feb]Censoring Time Limit: 10 Sec  Memory Limit: 128 MBSubmit: 723  Solved: 360[Subm ...

  2. css3中那些鲜为人知但又很有用的属性

    概述 这是我在写移动端页面的时候遇到的,css3中鲜为人知但又很有用的属性,记录下来,供以后开发时参考,相信对其他人也有用. tap-highlight-color 在移动端开发中,我们需要在用户轻按 ...

  3. [ActionScript 3.0] as3处理xml的功能和遍历节点

    as3比as2处理xml的功能增强了N倍,获取或遍历节点非常之方便,类似于json对像的处理方式. XML 的一个强大功能是它能够通过文本字符的线性字符串提供复杂的嵌套数据.将数据加载到 XML 对象 ...

  4. C# 开发代码标准

    开发标准文件 文件名称:C#开发规范 版 本:V2.0 前言 目的是为了规范每个人的编程风格,为确保系统源程序可读性,从而增强系统可维护性,制定下述编程规范,以规范系统各部分编程.系统继承的其它资源中 ...

  5. POJ 2491

    #include<iostream>#include<stdio.h>#include<string>#define MAXN 400using namespace ...

  6. vue教程2-07 自定义指令

    vue教程2-07 自定义指令 自定义指令: 一.属性: Vue.directive(指令名称,function(参数){ this.el -> 原生DOM元素 }); <div v-re ...

  7. 多线程的实现及常用方法_DAY23

    1:多线程(理解) (1)如果一个应用程序有多条执行路径,则被称为多线程程序. 进程:正在执行的程序. 线程:程序的执行路径,执行单元. 单线程:如果一个应用程序只有一条执行路径,则被称为单线程程序. ...

  8. php -- 获取函数参数

    ----- 015-parameter.php ----- <!DOCTYPE html> <html> <head> <meta http-equiv=&q ...

  9. Notification 浏览器的消息推送

    Notification 对象,存在于window上,可以生成一个通知对象以推送推送浏览器消息通知. 这玩意兼容性不咋地,实不实用看场景.对外用户的应用,自然是鸡肋功能,因为你无法知道用户使用的是哪家 ...

  10. Java总结:字符串详解

    更新时间:2018-1-6 21:20:39 更多请查看在线文集:http://android.52fhy.com/java/index.html String 字符串创建 String str1=& ...