您的位置:首页 > 编程语言 > Java开发

Java多线程技术研究(四)-Callable,Future/FutureTask,及Future设计模式

2017-06-21 19:54 447 查看
本篇博客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).
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: