Java 并发:Future FutureTask
Future
当向一个ExecutorService提交任务后可以获得一个Future对象,在该对象上可以调用get
,cancel
等命令来获取任务运行值或者是取消任务。下面是一个简单的计数任务:
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¬ifyAll或者基于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的更多相关文章
- java callable future futuretask
Runnbale封装一个异步运行的任务,可以把它想象成一个没有任何参数和返回值的异步方法.Callable和Runnable相似,但是它有返回值.Callable接口是参数化的类型,只有一个方法cal ...
- Java并发编程:Callable、Future和FutureTask
作者:海子 出处:http://www.cnblogs.com/dolphin0520/ 本博客中未标明转载的文章归作者海子和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置 ...
- java并发:获取线程执行结果(Callable、Future、FutureTask)
初识Callable and Future 在编码时,我们可以通过继承Thread或是实现Runnable接口来创建线程,但是这两种方式都存在一个缺陷:在执行完任务之后无法获取执行结果.如果需要获取执 ...
- Java并发:Callable、Future和FutureTask
Java并发编程:Callable.Future和FutureTask 在前面的文章中我们讲述了创建线程的2种方式,一种是直接继承Thread,另外一种就是实现Runnable接口. 这2种方式都有一 ...
- (转)Java并发编程:Callable、Future和FutureTask
Java并发编程:Callable.Future和FutureTask 在前面的文章中我们讲述了创建线程的2种方式,一种是直接继承Thread,另外一种就是实现Runnable接口. 这2种方式都有一 ...
- Java并发编程:Callable、Future和FutureTask(转)
Java并发编程:Callable.Future和FutureTask 在前面的文章中我们讲述了创建线程的2种方式,一种是直接继承Thread,另外一种就是实现Runnable接口. 这2种方式都有一 ...
- Java并发编程原理与实战三十一:Future&FutureTask 浅析
一.Futrue模式有什么用?------>正所谓技术来源与生活,这里举个栗子.在家里,我们都有煮菜的经验.(如果没有的话,你们还怎样来泡女朋友呢?你懂得).现在女票要你煮四菜一汤,这汤是鸡汤, ...
- Java 并发编程——Callable+Future+FutureTask
Java 并发编程系列文章 Java 并发基础——线程安全性 Java 并发编程——Callable+Future+FutureTask java 并发编程——Thread 源码重新学习 java并发 ...
- 15、Java并发编程:Callable、Future和FutureTask
Java并发编程:Callable.Future和FutureTask 在前面的文章中我们讲述了创建线程的2种方式,一种是直接继承Thread,另外一种就是实现Runnable接口. 这2种方式都有一 ...
- 007 Java并发编程:Callable、Future和FutureTask
原文https://www.cnblogs.com/dolphin0520/p/3949310.html Java并发编程:Callable.Future和FutureTask 在前面的文章中我们讲述 ...
随机推荐
- JS: 数组的循环函数
JS 数组相关的循环函数,用得挺多,所以有些坑还是要去踩一下,先来看一道面试题. 注意:下面提到的不改变原数组仅针对基本数据类型. 面试题 模拟实现数组的 map 函数. 心中有答案了吗?我的答案放在 ...
- 解决修改css或js文件,浏览器缓存更新问题。
在搜索引擎中搜索关键字.htaccess 缓存,你可以搜索到很多关于设置网站文件缓存的教程,通过设置可以将css.js等不太经常更新的文件缓存在浏览器端,这样访客每次访问你的网站的时候,浏览器就可以从 ...
- POJ 2552
#include<iostream> #include<stdio.h> using namespace std; ,,,,,,,,,,,,,,,,,,,,,,,,,,,,,, ...
- GCC C语言 DLL范例,含源码
作者:小白救星 编译:gcc -c -DBUILDING_HZ_DLL1 hzdll1.c gcc -shared -o hzdll1.dll hzdll1.o -Wl,--kil ...
- CentOS7.x编译安装nginx,实现HTTP2
网站使用HTTP2有助于网站加速及更安全,要配置HTTP2必须满足两个条件:①openssl的版本必须在1.0.2e及以上.②nginx的版本必须在1.9.5以上 一.准备工作 配置HTTP2之前需 ...
- Win7删除网络位置那些不用的网络位置(驱动器)
1.初始状态: 映射成功的网络位置如下图 2.要删除这个网络位置:点击"打开网络和共享中心",然后如下图设置: 3.重启电脑之后,删除的"网络位置"不会在资源管 ...
- configure: error: You need a C++ compiler for C++ support.[系统缺少c++环境]
一.错误configure: error: You need a C++ compiler for C++ support.二.安装c++ compiler情况1.当您的服务器能链接网络时候[联网安装 ...
- c++处理类型与自定义数据结构
1.typedef 类型别名 有时我们在阅读c++程序时,发现一些未见过的类型,这实际上就是typedef导致的,使用很简单,如下: typedef int wayne; wayne a = , b ...
- Android中常见的对话框
1. 普通对话框 public void click01(View view){ AlertDialog.Builder builder = new AlertDialog.Builder(this) ...
- [转]cximage双缓冲绘图 .
1.起因 本来是想用gdi绘图的,但是一想到用gdi+libpng,还要自己处理一些比如alpha的效果之类的巨麻烦(而且涉及到处理每一个像素点的计算,一般都很耗时),我对自己处理像素点的能力一直持有 ...