本篇博客Java多线程中另一块重要的内容:Callable,Future,FutureTask,及Future设计模式的模拟实现。
考虑这样一种场景: 网上购物,提交订单后,在收货的这段时间里无需一直在家里等候,可以先干别的事情。类推到程序设计中时,当提交请求时,期望得到答复时,如果这个答复可能很慢。传统的做法一直等待直到收到应答,可能才会去做后续的事情。在Java中提供Callable和Future机制实现了异步调用,即主线程为得到某个答复,创建一个子线程等待该答复,然后主线程在这个过程中干其他事情,办完其它事情后再去获得答复。我们把这种设计称为Future设计模式。
1、Callable和Runnable
在Java多线程技术研究(一) 中,Callable和Runnable一样,都是实现多线程的一种方法,但是Runnable不会返回结果,并且无法抛出带返回结果的异常,而Callable功能更加强大一些,线程执行结束后可以返回结果。这个结果可以通过Future或FutureTask拿到该线程的返回值。
Callable接口call()方法返回值为泛型,可以返回执行结果,并且还可以抛出异常:
public interface Callable<V> {
/**
* Computes a result, or throws an exception if unable to do so.
*
* @return computed result
* @throws Exception if unable to compute a result
*/
V call() throws Exception;
}
Runnable接口run()方法返回值为void型,显然不可获得其执行结果
public
interface Runnable {
/**
* When an object implementing interface <code>Runnable</code> is used
* to create a thread, starting the thread causes the object's
* <code>run</code> method to be called in that separately executing
* thread.
* <p>
* The general contract of the method <code>run</code> is that it may
* take any action whatsoever.
*
* @see java.lang.Thread#run()
*/
public abstract void run();
}
2、Callable和Future获取线程执行结果
Future接口中定义有如下方法:
1)cancel()
boolean cancel(boolean mayInterruptIfRunning);
取消任务的执行,mayInterruptRunning参数表示是否中断执行中的线程。
2)isCancelled()
boolean isCancelled();
任务是否已取消,如果在正常完成前已取消返回true。
3)isDone()
boolean isDone();
任务是否已完成,不管该任务是正常停止,异常导致,主动终止,均返回true。
4)get()
V get() throws InterruptedException, ExecutionException;
获取异步执行的结果,如果任务没有执行结束,此方法会阻塞直到任务完成。
5) get(long timeout, TimeUnit unit)
V get(long timeout, TimeUnit unit)
throws InterruptedException, ExecutionException, TimeoutException;
其中timeout为超时时间,unit为时间单位,如果在阻塞timeout时间,仍没有获得返回结果,get()方法会抛出TimeoutException异常。
下面来看利用Callable和Future获取线程执行结果
package com.wygu.thread.study;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import java.util.concurrent.FutureTask;
public class MultiThreadCallable {
public static void main(String []argv){
TaskCallable tCallable = new TaskCallable();
ExecutorService executorService = Executors.newSingleThreadExecutor();
//将任务提交到线程池中
Future<Integer> future =executorService.submit(tCallable);
//主线程可以进行其他事情
try {
Integer result = future.get();
System.out.println("子线程返回结果为:"+result);
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
}
}
}
class TaskCallable implements Callable<Integer>{
@Override
public Integer call(){
int num=10;
try {
Thread.sleep(3000);
num+=10;
} catch (InterruptedException e) {
e.printStackTrace();
}
return num;
}
}
子线程返回结果为:20
3、Callable和FutureTask
首先看一下类FutureTask的实现:
public class FutureTask<V> implements RunnableFuture<V>
FutureTask实现了接口RunnableFuture,而对于RunnableFuture的实现:
public interface RunnableFuture<V> extends Runnable, Future<V>
RunnableFuture不仅实现了Future中的方法,还实现了Runnable中run方法。
因而FutureTask不仅可以直接提交给Executor执行,还可以调用线程直接执行FutureTask中实现的run()方法.
通过FutureTask的下述两种构造方法,利用FutureTask可以获得Runnable的中执行结果。
public FutureTask(Callable<V> callable) {
if (callable == null)
throw new NullPointerException();
this.callable = callable;
this.state = NEW; // ensure visibility of callable
}
public FutureTask(Runnable runnable, V result) {
this.callable = Executors.callable(runnable, result);
this.state = NEW; // ensure visibility of callable
}
下面来看利用Callable和FutureTask获取线程执行结果
public class MultiThreadCallable {
public static void main(String []argv){
TaskCallable tCallable = new TaskCallable();
FutureTask<Integer> futureTask = new FutureTask<Integer>(tCallable);
new Thread(futureTask,"带返回值的线程").start();
//主线程可以进行其他事情
try {
Integer result = futureTask.get();
System.out.println("子线程返回结果为:"+result);
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
}
}
}
class TaskCallable implements Callable<Integer>{
@Override
public Integer call(){
int num=10;
try {
Thread.sleep(3000);
num+=10;
} catch (InterruptedException e) {
e.printStackTrace();
}
return num;
}
}
程序执行结果:
子线程返回结果为:20
4、Future设计模式的模拟实现
在前面的场景中:网上挑好物品后,提交表单会获得一个订单,然后就可以等待收货,在收货的这段时间里无需一直在家里等候,可以先干别的事情。
图片来源于:http://blog.csdn.net/ghuil/article/details/41048017
在传统多线程模式,采用的是同步调用方式,Client发出call请求后,会一直等待服务端给予真实的应答数据,只有当Client等待到应答后,才能进行其它事情。而在Future设计模式调用中,Client发出call请求后,Server端会立即给予Client端一个应答,此时的应答数据可以说是一个假数据(不是真实的结果),Client端收到Server端的应答后,就去处理其它事情了,而Server端内部可以慢慢的生产数据,完成后把放置在对象中,等待Client端获取。
下面给出Future模式的程序实现
1)FutureClient的实现
FutureClient主要功能包括:获得假数据FutureData,获得真实数据RealData
package com.wygu.futurePattern;
public class FutureClient<T> {
private T requestData = null;
public FutureClient(T requestdata){
this.requestData = requestdata;
}
//创建方法,获得真实的数据
public FutureData<T> callForBack(){
final FutureData<T> fData = new FutureData<T>();
new Thread(new Runnable() {
@Override
public void run() {
RealData<T> realData = new RealData<T>(requestData);
fData.setRealData(realData);
}
}).start();
return fData;
}
}
2)Data的实现
接口Data中,定义一个获得数据的方法getResult(),在FutureData,RealData均重新实现该接口。其中FutureData可以说是对RealData的代理,它会封装RealData的getResult()方法。
Data接口定义
package com.wygu.futurePattern;
public interface Data<T> {
public T getResult() throws InterruptedException;
}
FutureData的实现
package com.wygu.futurePattern;
public class FutureData<T> implements Data<T>{
private RealData<T> realData = null;
private volatile boolean isReady = false;
public synchronized void setRealData(RealData<T> realData) {
//判断真实数据是否已完成
if(isReady){
return;
}
this.realData = realData;
//数据已生产好
this.isReady = true;
//唤醒等待的线程
notifyAll();
}
@Override
public synchronized T getResult() throws InterruptedException {
if(!isReady){
wait();//阻塞调用该方法的线程,产生真实的数据
}
return realData.getResult();
}
}
RealData的实现
package com.wygu.futurePattern;
@SuppressWarnings("rawtypes")
public class RealData<T> implements Data{
private T realData = null;
public RealData(T realData) {
//使用sleep模拟生产真实数据很慢
try {
Thread.sleep(10000);
} catch (InterruptedException e) {
e.printStackTrace();
}
this.realData = realData;
}
@Override
public T getResult() throws InterruptedException{
//获取生产的真实数据
return realData;
}
}
3)测试运行
package com.wygu.futurePattern;
public class Main {
public static void main(String[] args) {
long start = System.currentTimeMillis();
FutureClient<String> futureClient = new FutureClient<String>("Hi,Future Pattern");
//主线程创建Client实例,去请求真实数据
FutureData<String> futureData = futureClient.callForBack();
//让主线程沉睡一段时间,在去获得真实数据
try {
Thread.sleep(9000);
} catch (InterruptedException e) {
e.printStackTrace();
}
try {
System.out.println("Client 获取真实数据:"+futureData.getResult()+",共耗时:"+(System.currentTimeMillis()-start)+"毫秒");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
运行结果为:Client 获取真实数据:Hi,Future Pattern,共耗时:9997毫秒
修改主线程做其他工作时间(沉睡时间)为Thread.sleep(11000),运行结果为:Client 获取真实数据:Hi,Future Pattern,共耗时:10994毫秒。假设主线程的处理其它事物的时间为t1,Server端产生真实数据的时间为t2,则主线程运行结束的时间为max(t1,t2).