文章目录
1 引入
假设有个任务需要执行比较长的的时间,通常需要等待任务执行结束或者出错才能返回结果,在此期间调用者只能陷入阻塞苦苦等待,对此,Future设计模式提供了一种凭据式的解决方案。。在我们日常生活中,关于凭据的使用非常多见,比如你去某西服手工作坊想订做一身合体修身的西服,西服的制作过程比较漫长,少则一个礼拜,多则一个月,你不可能一直待在原地等待,一般来说作坊会为你开一个凭据,此凭据就是Future,在接下来的任意日子里你可以凭借此凭据到作坊获取西服。
自JDK1.5起,Java提供了比较强大的Future接口,在JDK1.8时更是引入了CompletableFuture,其结合函数式接口可实现更强大的功能
2 简单实现
2.1 接口定义
2.1.1 Future接口设计
Future提供了获取计算结果和判断任务是否完成的两个接口,其中获取计算结果将会导致调用阻塞(在任务还未完成的情况下)
/**
* @author wyaoyao
* @date 2021/4/10 11:53
* Future提供了获取计算结果和判断任务是否完成的两个接口,其中获取计算结果将会导致调用阻塞(在任务还未完成的情况下)
* 其中泛型为结果的类型
*/
public interface Future<T> {
/***
* 返回任务执行结果
* @return
* @throws InterruptedException
*/
T get() throws InterruptedException;
/**
* 是否任务完成
* @return
*/
boolean done();
}
2.1.2 Runnbale接口替代接口:Task接口
Task接口主要是提供给调用者实现计算逻辑之用的,
可以接受一个参数并且返回最终的计算结果,这一点非常类似于JDK1.5中的Callable接口。
Runnable接口本身是不支持返回值和形参的,所以定义此接口替代Runnable接口
/**
* @author wyaoyao
* @date 2021/4/10 11:58
* Task接口主要是提供给调用者实现计算逻辑之用的,
* 可以接受一个参数并且返回最终的计算结果,这一点非常类似于JDK1.5中的Callable接口
*/
@FunctionalInterface
public interface Task<I,R> {
/**
* 根据输入返回计算结果(Runnable接口本身是不支持返回值和形参的,所以定义此接口替代Runnable接口)
*
* @param input
* @return
*/
R get(I input);
}
2.1.3 FutureService接口
- FutureService主要用于提交任务,提交的任务主要有两种,第一种不需要返回值,第二种则需要获得最终的计算结果
- JDK 8中不仅支持default方法还支持静态方法,JDK 9甚至还支持接口私有方法
/**
* @author wyaoyao
* @date 2021/4/10 11:55
* FutureService主要用于提交任务,提交的任务主要有两种,第一种不需要返回值,第二种则需要获得最终的计算结果
* 。FutureService接口中提供了对FutureServiceImpl构建的工厂方法,
* JDK 8中不仅支持default方法还支持静态方法,JDK 9甚至还支持接口私有方法
*/
public interface FutureService<I,R> {
/**
* 提交不需要返回值任务
* @param runnable
* @return
*/
Future<?> submit(Runnable runnable);
/**
* 提交需要返回值的任务,其中Task接口代替了Runnable接口
* @param task
* @param input
* @return
*/
Future<R> submit(Task<I,R> task, I input);
}
2.2 接口实现
2.2.1 Future接口实现: FutureTask
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) {
while (!isDone) {
// 如果任务没有执行完成,则等待任务执行完成
// 何时唤醒:当任务执行完成:参见下面的finish方法
LOCK.wait();
}
}
return result;
}
@Override
public boolean done() {
return this.isDone;
}
/**
* finish 方法用于设置任务执行结果
*
* @param result
*/
protected void finish(T result) {
// 多线程下,需要加锁
synchronized (LOCK) {
if (isDone) {
return;
}
// 完成那就设置结果,并更新isDone为true
this.result = result;
this.isDone = true;
// 可能等待获取结果的线程,需要唤醒这些线程
LOCK.notifyAll();
}
}
}
FutureTask中充分利用了线程间的通信wait和notifyAll,当任务没有被完成之前通过get方法获取结果,调用者会进入阻塞,直到任务完成并接收到其他线程的唤醒信号,finish方法接收到了任务完成通知,唤醒了因调用get而进入阻塞的线程。
2.2.2 FutureService实现
该实现类主要所用就是当提交任务之后,创建一个线程去执行该任务。
public class FutureServiceImpl<I, R> implements FutureService<I, R> {
/**
* 指定线程名字的前缀
*/
private final static String THREAD_PREFIX = "FUTURE_THREAD";
/**
* 累加器
*/
private final AtomicInteger nextCounter = new AtomicInteger(0);
@Override
public Future<?> submit(Runnable runnable) {
// 1 定义一个FutureTask,返回给用户(FutureTask就是一个凭据,用户可以通过FutureTask来获取线程执行的结果)
FutureTask<Void> futureTask = new FutureTask<>();
// 构造一个线程去执行提交的任务
new Thread(() -> {
runnable.run();
// 任务执行结束后,将null设置给FutureTask
futureTask.finish(null);
}, getNextName()).start();
// 返回给用户(FutureTask就是一个凭据,用户可以通过FutureTask来获取线程执行的结果)
return futureTask;
}
@Override
public Future<R> submit(Task<I, R> task, I input) {
// 1 定义一个FutureTask,返回给用户(FutureTask就是一个凭据,用户可以通过FutureTask来获取线程执行的结果)
FutureTask<R> futureTask = new FutureTask<>();
// 构造一个线程去执行提交的任务
new Thread(()->{
R r = task.get(input);
// 执行结束后,将执行结果设置给FutureTask
futureTask.finish(r);
}, getNextName()).start();
// 返回给用户(FutureTask就是一个凭据,用户可以通过FutureTask来获取线程执行的结果)
return futureTask;
}
private String getNextName() {
return THREAD_PREFIX + "_" + nextCounter.getAndIncrement();
}
}
在FutureServiceImpl的submit方法中,分别启动了新的线程运行任务,起到了异步的作用,在任务最终运行成功之后,会通知FutureTask任务已完成。
3 测试
3.1 无返回值测试
public static void main(String[] args) throws InterruptedException {
FutureServiceImpl<Void, Void> service = new FutureServiceImpl<>();
Future<?> future = service.submit(() -> {
try {
TimeUnit.SECONDS.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("线程执行结束");
});
Object o = future.get();
// 一旦获取到结果,就可以拿到结果干别的了,这里就简单判断一下
// 测试的无返回值,所以输出为true
System.out.println(o == null);
}
3.2 测试有返回值
public static void main(String[] args) throws InterruptedException {
FutureServiceImpl<String,Integer> futureService = new FutureServiceImpl<>();
Future<Integer> future = futureService.submit(input -> {
try {
TimeUnit.SECONDS.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
// 就返回输入字符传的长度
return input.length();
},"HelloWorld");
// 获取结果,获取不到就会等待
Integer o = future.get();
// 一旦获取到结果,就可以拿到结果干别的了,这里就简单输出一下:10
System.out.println(o);
}
通过测试,主线程就可以先干自己的事情,等到想获取结果的时候,通过Futute去获取结果即可,获取不到结果(说明任务还未执行完成),就会进入阻塞等待结果。
Future模式设计已讲解完毕,虽然我们提交任务时不会进入任何阻塞,但是当调用者需要获取结果的时候,还是有可能陷入阻塞直到任务完成
4 增强FutureService使其支持回调:
使用任务完成时回调的机制可以让调用者不再进行显式地通过get的方式获得数据而导致进入阻塞,可在提交任务的时候将回调接口一并注入,在这里对FutureService接口稍作修改:
- 先定义一个回调接口
@FunctionalInterface
public interface CallBack<T> {
void call(T t);
/**
* 提供一个默认实现
* @param <T>
*/
static class DefaultCallBack<T> implements CallBack<T> {
@Override
public void call(T t) {
// do nothing
}
}
}
也可以使用java8提供的Consumer接口
- futureService接口改造:,增加了一个Callback参数,主要用来接受并处理任务的计算结果,当提交的任务执行完成之后,会将结果传递给Callback接口进行进一步的执行,这样在提交任务之后不再会因为通过get方法获得结果而陷入阻塞。
Future<R> submit(Task<I,R> task, I input,CallBack<R> callBack);
实现类修改:
@Override
public Future<R> submit(Task<I, R> task, I input,CallBack<R> callBack) {
// 1 定义一个FutureTask,返回给用户(FutureTask就是一个凭据,用户可以通过FutureTask来获取线程执行的结果)
FutureTask<R> futureTask = new FutureTask<>();
// 构造一个线程去执行提交的任务
new Thread(()->{
R r = task.get(input);
// 执行结束后,将执行结果设置给FutureTask
futureTask.finish(r);
// 完成之后,主动调用使用定义的回调函数,避免阻塞
callBack.call(r);
}, getNextName()).start();
// 返回给用户(FutureTask就是一个凭据,用户可以通过FutureTask来获取线程执行的结果)
return futureTask;
}
测试:
public static void main(String[] args) throws InterruptedException {
FutureServiceImpl<String,Integer> futureService = new FutureServiceImpl<>();
Future<Integer> future = futureService.submit(input -> {
try {
TimeUnit.SECONDS.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
// 就返回输入字符传的长度
return input.length();
},"HelloWorld",integer -> System.out.println(integer));
}
这样主线程在提交任务的时候顺便提交了一个任务完成时候的回调,这样当任务执行结束后,便会自动调用回调,不需要主线程等待任务执行完成(阻塞),自己去主动调用这部分逻辑
798

被折叠的 条评论
为什么被折叠?



