导语
假设,在一个使用场景中有一个任务需要执行比较长的时间,通常需要等待任务执行结束之后或者是中途出错之后才能返回结果。在这个期间调用者只能等待,对于这个结果Future设计模式提供了一种凭据式的解决方案。在日常生活中,这种方案也是存在的。例如去洗衣店洗衣服,当你把衣服放到洗衣店,等他洗完需要一段时间,这个时候洗衣店就会给你一凭证,你可以通过这个凭证到时候去取洗好的衣服。这个例子就是生活中的Future模式。
从JDK1.5开始,Java提供Future接口,在JDK1.8的时候引入的CompletableFuture。结合函数式的编程接口可以实现强大的功能。
文章目录
Future设计模式的实现
1.1 接口定义
1.1.1 Future 接口设计
Future 提供了获取计算结果和判断任务是否完成的两个接口,其中获取计算结果将会导致阻塞,也就是说在任务没有完成的情况下会等待任务执行。
/**
* @Classname Future
* @Description TODO
* @Date 2019/9/20 5:40 PM
* @Created by nihui
*/
public interface Future<T> {
//返回计算后的结果,该方法会进入到阻塞状态
T get() throws InterruptedException;
//判断任务是否已经被执行完成
boolean done();
}
1.1.2 FutureService 接口设计
FutureService主要用于提交任务,提交的任务主要有两种,一种是不需要返回值的,一种是需要返回值的。FutureService中提供了对于FutureServiceImpl构建的工厂方法,在JDK1.8中不仅支持default方法还支持静态方法,JDK1.9还可以支持接口私有方法。
/**
* @Classname FutureService
* @Description TODO
* @Date 2019/9/20 5:48 PM
* @Created by nihui
*/
public interface FutureService<IN,OUT> {
//提交不需要返回值的任务, Future.get方法返回为null
Future<?> submit(Runnable runnable);
//提交需要返回值的任务,其中Task接口代替了Runnable接口
Future<OUT> submit(Task<IN, OUT> task,IN input);
//使用静态方法创建一个FutureService的实现
static <T,R> FutureService<T,R> newService(){
return new FutureServiceImpl<>();
}
}
1.1.3 Task接口实现
Task 接口主要是提供给调用者实现计算逻辑,可以接受一个参数并且返回最终的计算结果。这点类似于Callable接口。
/**
* @Classname Task
* @Description TODO
* @Date 2019/9/20 5:56 PM
* @Created by nihui
*/
@FunctionalInterface
public interface Task<IN,OUT> {
//给定一个参数,经过计算返回值
OUT get(IN input);
}
1.2 程序实现
1.2.1 FutureTask
FutureTask 是Future的一个实现,当然除了实现Future中的方法之外,还增加了finish方法,这方法主要是用来接收任务被完成的通知,FutureTask设计如下。
/**
* @Classname FutureTask
* @Description TODO
* @Date 2019/9/20 6:42 PM
* @Created by nihui
*/
public class FutureTask<T> implements Future<T> {
//计算结果
private T result;
//任务是否完成
private boolean isDone = false;
//定义对象锁
private final Object LOCK = new Object();
@Override
public T get() throws InterruptedException {
synchronized (LOCK){
//当任务还没有完成的时候,调用get方法会被挂起而进入阻塞
while (!isDone){
LOCK.wait();
}
}
//返回最终计算结果
return result;
}
//finish方法主要用于为FutureTask设置计算结果
protected void finish(T result){
synchronized (LOCK){
//balking设计模式
if (isDone){
return;
}
//计算完成,为result指定结果,并将isDone设置为True,同时唤醒阻塞中的线程
this.result = result;
this.isDone = true;
LOCK.notifyAll();
}
}
@Override
public boolean done() {
return isDone;
}
}
FutureTask 充分使用了线程间的通信wait和notifyAll,当任务没有被完成之前通过get方法获取结果,调用者会进入阻塞,直到任务完成并接收到其他线程的唤醒信号,finish方法接收了任务完成通知,唤醒了因为调用了get方法而进入阻塞的线程。
1.2.2 FutureServiceImpl
/**
* @Classname FutureServiceImpl
* @Description TODO
* @Date 2019/9/20 6:51 PM
* @Created by nihui
*/
public class FutureServiceImpl<IN,OUT> implements FutureService<IN,OUT> {
//为执行的线程指定名称前缀
private final static String FUTURE_THREAD_PREFIX = "FUTURE-";
private final AtomicInteger nextCounter = new AtomicInteger(0);
private String getNextName(){
return FUTURE_THREAD_PREFIX + nextCounter.getAndIncrement();
}
@Override
public Future<?> submit(Runnable runnable) {
final FutureTask<Void> future = new FutureTask<>();
new Thread(()->{
runnable.run();
//任务执行结束之后将null作为结果传回future
future.finish(null);
},getNextName()).start();
return future;
}
@Override
public Future<OUT> submit(Task<IN, OUT> task, IN input) {
final FutureTask<OUT> future = new FutureTask<>();
new Thread(()->{
OUT result = task.get(input);
//任务执行结束之后,将真实的结果通过finish方法传递给future
future.finish(result);
},getNextName()).start();
return future;
}
}
1.3 Future 的使用以及技巧总结
直译Future表示未来,主要是将一些耗时的操作交给一个线程去执行,从而达到异步操作的目的,提交线程在提交任务和获得计算结果的过程中可以进行其他的任务执行,而不至于等待结果返回。
由于在接口中定义了两种交互方式,所以可以对两种情况进行测试。
无返回值任务
/**
* @Classname Test
* @Description TODO
* @Date 2019/9/20 7:14 PM
* @Created by nihui
*/
public class Test {
public static void main(String[] args) throws InterruptedException {
FutureService<Void,Void> service = FutureService.newService();
Future<?> future = service.submit(()->{
try {
TimeUnit.SECONDS.sleep(10);
}catch (InterruptedException e){
e.printStackTrace();
}
System.out.println("I am finish done.");
});
future.get();
}
}
第二个方法会返回一个字符串长度
public static void main(String[] args) throws InterruptedException {
FutureService<String, Integer> service = FutureService.newService();
Future<Integer> future = service.submit(input -> {
try {
TimeUnit.SECONDS.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("I am finish done.");
return input.length();
},"hello");
System.out.println(future.get());;
}
1.4 增强FutureService支持回调
&emps;使用任务完成时回调机制可以让调用者不在进行显式的调用,通过get方法获取结果,而导致阻塞,可以在提交任务的时候将一个回调接口一并注入,所以要对接口做如下的修改。增加如下的方法
public Future<OUT> submit(Task<IN,OUT> task, IN input, CallBack<OUT> callback){
final FutureTask<OUT> future = new FutureTask<>();
new Thread(()->{
OUT result =task.get(input);
future.finish(result);
if (null !=callback){
callback.getFuture(result);
}
},getNextName()).start();
return future;
}
完成之后在进行调用
public static void main(String[] args) throws InterruptedException {
FutureService<String, Integer> service = FutureService.newService();
Future<Integer> future = service.submit(input -> {
try {
TimeUnit.SECONDS.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("I am finish done.");
return input.length();
},"hello",System.out::println);
}
总结
上面通过一个小Demo实现了Future模式。当然对于Future模式还可以做拓展。从上面课可以看到在解决高并发问题的时候,不同的场景有不同的解决方案,对于这些解决方案,需要选择合适的方案解决合适场景的合适问题。当然也可以根据自己的需求解决问题。设计出适合自己的功能方式。