http://www.silencedut.com/2016/06/15/Callable%E5%92%8CFuture%E3%80%81FutureTask%E7%9A%84%E4%BD%BF%E7%94%A8/

 

并发的学习与使用系列 第四篇

在Java中,开启一个线程的唯一方式是,是通过Thread的start方法,并且在线程中执行的Runnable的run方法。无论是线程池还是接下来要介绍的Callable,Future还是线程池,最核心最根本的还是调用到Thread.start()–>Runnable.run(),其他的类的出现可以认为是更方便的使用Thread和Runnable,以此为核心会更容易理解Java的并发框架。

虽然Thread和Runnable类使得多线程编程简单直接,但有一个缺陷就是:在执行完任务之后无法获取执行结果。如果需要获取执行结果,就必须通过共享变量或者使用线程通信的方式来达到效果,这样使用起来就比较麻烦。因此从Jdk1.5开始,有了一系列的类的出现来解决这些问题,如Callable和Future,FutureTask以及下篇要讲到的线程池从使用到原理学习Java线程池

而自从Java 1.5开始,就提供了Callable和Future以及FutureTask,通过它们可以在任务执行完毕之后得到任务执行结果。

实现原理

Thread和Runnable

首先看Thread和Runnable的实现多线程任务的原理。

以下是简化后的代码,为了方便理解。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
public class Thread implements Runnable {
Runnable target;
 
public Thread(Runnable runnable) {
 
target = Runnable;
 
//省略其他初始化线程的任务
}
 
public void start() {
nativeCreate(this, stackSize, daemon);//native方法开启多线程,并调用run方法
}
 
public void run() {
if (target != null) {
target.run();
}
}
}

可以看出target是一个Runnble对象,通过一个典型的装饰模式来扩展Runnable,如果不传入,默认为null,需要自己实现run方法来在新线程里执行任务,否则线程不会做任何事情就结束。所以无论怎么变化,最终都是Thread的start方法开启新线程,run方法在这个新开启的线程里执行任务,当然run方法也可以单独调用,但所在线程是调用者的线程。

装饰者模式的典型特点:装饰后的类和被装饰的类,类型不变(继承Runnable),提供新的行为,方法(start()等),关于设计模式的详细细节,见常见的设计模式解读

Callable和Future,FutureTask

先通过UML图来看它们和Thrad,Runnable之间的关系:

Callable与Runnable的功能大致相似,Callable中有一个call()函数,但是call()函数有返回值,而Runnable的run()函数不能将结果返回给客户程序。

Future就是对Callable任务的执行结果进行取消、查询是否完成、获取结果、设置结果操作。其中的get()方法就是用来得到Callable的call()结果的。

FutureTask是Future的具体实现类,实现了get()等方法来对控制Callabel的行为,又因为Thread只能执行Runnable,所以FutureTask实现了Runnable接口。

因为FutureTask需要在Thread中执行,所以需要在run()方法中完成具体的实现:

1
2
3
4
5
6
7
8
9
10
//简化后的代码,为了方便理解
public void run() {
Callable<V> c = callable;
 
if (c != null && state == NEW) {
V result;
result = c.call();
set(result);
}
}

通过get方法来获取结果,get()是个阻塞方法,直到结果返回,或者中断发生。还可以通过get(long timeout, TimeUnit unit)方法控制等待结果的最大时间。

1
2
3
4
5
6
public V get() throws InterruptedException, ExecutionException {
int s = state;
if (s <= COMPLETING)
s = awaitDone(false, 0L);//阻塞等待
return report(s);
}

可以看出FutureTask的run方法实际的任务是在Callable的call中完成,FutureTask的实现方式采用了适配器模式来完成。

如果构造函数传入的是Runnable,则通过Executors的静态函数callable(Runnable task,…)将Runnable转换为Callable类型:

1
2
3
4
5
6
public static Callable callable(Runnable task, T result) {
 
if (task == null) throw new NullPointerException();
 
return new RunnableAdapter(task, result);
}

适配器模式的典型特点:包装另一个对象(包装了Callable),提供不同的接口(Runnable接口)

Callable和Future,FutureTask经常容易让人记忆混乱,理解后就知道了其实Future和FutureTask就是用来将Callable包装成一个Runnable,这样才能够在Thread中执行,同时提供将结果返回的功能,三个类总是同时出现,整体理解为是一个可以得到返回结果的Runnable。

使用

那么怎么使用这些类呢呢?由于FutureTask实现了Runnable,因此它既可以通过Thread包装来直接执行,也可以提交给ExecuteService来执行,在Thread中,就像使用Runnable一样。

关于线程池的更多细节将在下一篇文章中进行讲解

示例代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
/**
* Created by SilenceDut on 16/7/15.
**/
 
public class FutureTest {
 
public static void main(String[] args) {
FutureTest futureTest = new FutureTest();
futureTest.useExecutor();
futureTest.useThread();
}
 
private void useExecutor() {
 
SumTask sumTask = new SumTask(1000);
ExecutorService executor = Executors.newCachedThreadPool();
 
FutureTask<Integer> futureTask = new
FutureTask<Integer>(sumTask);
 
executor.submit(futureTask);
executor.shutdown();
 
try {
System.out.println(Thread.currentThread().getName()+"::useExecutor运行结果" + futureTask.get());
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
}
}
 
private void useThread() {
SumTask sumTask = new SumTask(500);
 
FutureTask<Integer> futureTask = new FutureTask<Integer>(sumTask) {
@Override
protected void done() {
super.done();
try {
// 这是在后台线程
System.out.println(Thread.currentThread().getName()+"::useThread运行结果" + get());
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
}
}
};
 
Thread thread = new Thread(futureTask);
thread.start();
 
try {
//这是在主线程,会阻塞
System.out.println(Thread.currentThread().getName()+"::useThread运行结果" + futureTask.get().getName());
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
}
}
 
class SumTask implements Callable<Integer> {
int number;
 
public SumTask(int num) {
this.number = num;
}
 
@Override
public Integer call() throws Exception {
 
System.out.println(Thread.currentThread().getName());
Thread.sleep(5000);
 
int sum = 0;
for (int i = 0; i < number; i++) {
sum += i;
}
return sum;
}
}
}

结果:

pool-1-thread-1
main::useExecutor运行结果499500
Thread-0
main::useThread运行结果124750
Thread-0::useThread运行结果124750

FutureTask.get()是阻塞的,useExecutor()和useThread()也会阻塞。这里只是说明FutureTask.get()所在的线程是调用者所在的线程,在Android中使用的话,一般是在FutureTask的done方法中get,这时get就是在后台线程调用了,然后通过Handler通知到UI或其他线程。我写了一个AysncTask替代库AsyncTaskScheduler,实现了通过线程池调用和单个线程调用的具体方式,里面有具体的实现方式。

Callable和Future、FutureTask的使用的更多相关文章

  1. Callable, Runnable, Future, FutureTask

    Java并发编程之Callable, Runnable, Future, FutureTask Java中存在Callable, Runnable, Future, FutureTask这几个与线程相 ...

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

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

  3. Java多线程21:多线程下的其他组件之CyclicBarrier、Callable、Future和FutureTask

    CyclicBarrier 接着讲多线程下的其他组件,第一个要讲的就是CyclicBarrier.CyclicBarrier从字面理解是指循环屏障,它可以协同多个线程,让多个线程在这个屏障前等待,直到 ...

  4. Callable、Future、RunnableFuture、FutureTask的原理及应用

    1. Callable.Future.RunnableFuture.FutureTask的继承关系 在多线程编程中,我们一般通过一个实现了Runnable接口的对象来创建一个线程,这个线程在内部会执行 ...

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

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

  6. Callable、Future和FutureTask

    创建线程的2种方式,一种是直接继承Thread,另外一种就是实现Runnable接口.这2种方式都有一个缺陷就是:在执行完任务之后无法获取执行结果. 如果需要获取执行结果,就必须通过共享变量或者使用线 ...

  7. Callable与Future、FutureTask的学习 & ExecutorServer 与 CompletionService 学习 & Java异常处理-重要

    Callable是Java里面与Runnable经常放在一起说的接口. Callable是类似于Runnable的接口,实现Callable接口的类和实现Runnable的类都是可被其他线程执行的任务 ...

  8. Java并发:Callable、Future和FutureTask

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

  9. Runnable、Callable、Future和FutureTask用法

    http://www.cnblogs.com/dolphin0520/p/3949310.html java 1.5以前创建线程的2种方式,一种是直接继承Thread,另外一种就是实现Runnable ...

随机推荐

  1. 虚拟机的ip地址为什么会发生变化

    因为虚拟机在NAT模式下由Vmware8虚拟网卡提供虚拟机的IP分配,网桥模式下由Vmware1来提供IP分配.它们都相当于 一个小型的DHCP服务器,除非改动虚拟机的网络连接方式,或动了虚拟网卡服务 ...

  2. sed 双引号 单引号的区别

    a="abcd" b="abc" sed -i '/$a/ s/$/$b/' test.a 我想在test.a中匹配以”abcd“开头的行,然后在行尾加入”ab ...

  3. mysql定时任务event——清理过期数据

    需要删除数据的表名:t_req_log 建表sql CREATE TABLE `t_req_log` ( `id` ) NOT NULL AUTO_INCREMENT, `host` ) DEFAUL ...

  4. build/temp.linux-x86_64-2.7/_openssl.c:493:30: fatal error: openssl/opensslv.h: No such file or directory

    解决:apt-get install libssl-dev apt install python-dev(这个可能和那个错误关系不大)

  5. 猜数字游戏 在控制台运行--java详解!了;来玩

    import java.util.Scanner;//导入包 import java.util.Scanner; 注意格式 符号的使用 public class Demo{ //猜数字游戏 练习 pu ...

  6. 【python练习题】程序10

    #10.题目:暂停一秒输出,并格式化当前时间. import time print(time.time())#timestamp print(time.localtime(time.time()))# ...

  7. B-树 B+树复习总结

    一.B-树的定义 一棵m阶的B-树或为空树,或为具有以下特性的m叉树 1.树中每个结点至多有m棵子树 (m-1个关键字) 2.根结点至少有两棵子树 (至少有一个关键字) 3.除根节点的分支结点至少有f ...

  8. MySQL启动错误---发生系统错误/系统找不到指定的文件。

    今天启动mysql时,突然报错发生系统错误,系统找不到指定的文件.当时有点懵,安装mysql 之后,一直就没有修改过,怎么会报错呢?上网搜索了一下,重新安装一下mysql服务就可以了,现在也不知道什么 ...

  9. U68641 划水(swim.pas/c/cpp)

    U68641 划水(swim.pas/c/cpp) 题目背景 小小迪带你划水. 题目描述 原题 输入输出格式 输入格式: 第一行一个数 T. 接下来 T 行每行一个数表示 n 输出格式: 输出 T 行 ...

  10. servlet篇 之 servlet概念及其功能实现

    Servlet概念: 在JavaWeb中,把那些可以使用浏览器通过url的形式来访问的java类,叫做servlet. servlet就是java类中的一种,只不过这种java类有一个特殊的功能,就是 ...