# 《并发设计模式》第23章-承诺模式-承诺模式在FutureTask类中的应用
作者:冰河
星球:http://m6z.cn/6aeFbs (opens new window)
博客:https://binghe.gitcode.host (opens new window)
文章汇总:https://binghe.gitcode.host/md/all/all.html (opens new window)
源码获取地址:https://t.zsxq.com/0dhvFs5oR (opens new window)
沉淀,成长,突破,帮助他人,成就自我。
- 本章难度:★★☆☆☆
- 本章重点:了解承诺模式在Future类中的应用,掌握基于承诺模式优化项目的方法和技巧,进一步重点掌握承诺模式在实际项目场景中的应用,并能够结合自身项目实际场景思考如何将承诺模式灵活应用到自身实际项目中。
大家好,我是冰河~~
承诺模式可以实现并行执行多个业务关联性不强的业务,在执行具体业务逻辑时,基于承诺模式可以先开始一个任务的执行,并且得到一个用于获取这个任务执行结果的凭据对象,不用等到这个任务执行完毕再去执行其他的业务逻辑,如果需要获取执行任务的结果时,可以调用凭据对象的相关方法来获取结果数据。在JDK中,FutureTask类就是基于Promise模式实现的。
# 一、前文回顾
无论是在基于Promise模式优化社区电商项目,还是在基于Promise模式优化文件助手项目时,我们都使用到了JDK中的FutureTask类。例如,在基于Promise模式优化社区电商项目时,我们实现的Promisor类的代码如下所示。
public class FileSyncerPromisor {
private static final FileSyncerPromisor INSTANCE = new FileSyncerPromisor();
private FileSyncerPromisor(){}
public static FileSyncerPromisor getInstance(){
return INSTANCE;
}
public Future<FileSyncer> execute(FileSyncerConfig fileSyncerConfig){
FutureTask<FileSyncer> futureTask = new FutureTask<>(()->{
FileSyncer fileSyncer = new FileSyncerImpl();
fileSyncer.connect(fileSyncerConfig.getServerAddress(), fileSyncerConfig.getUsername(), fileSyncerConfig.getPassword(), fileSyncerConfig.getServerDir());
return fileSyncer;
});
FileSyncerThreadPool.execute(futureTask);
return futureTask;
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
可以看到,在FileSyncerPromisor类的execute()方法中,创建了一个futureTask对象,并且会将这个futureTask对象提交到线程池执行,未等任务执行完毕,即返回了futureTask对象,此时的futureTask对象就相当于是Promise模式中的凭据的凭据对象,随后,我们便可以通过futureTask对象的get()方法获取真正的业务结果数据。
# 二、FutureTask类结构
FutureTask类对象被提交到线程任务中执行时,后续为何能够通过FutureTask类对象获取结果数据呢?首先没来看下FutureTask类的结构关系图,如图23-1所示。
可以看到,FutureTask类实现了RunnableFuture接口,而RunnableFuture接口继承了Runnable接口和Future接口。所以,FutureTask类即实现了Runnable接口,又实现了Future接口,通过FutureTask类可以获取到线程任务执行的结果数据。
# 三、FutureTask类封装结果
搞清楚FutureTask的类结构关系后,我们再来分析下FutureTask类是如何封装结果数据的,首先来看看FutureTask类的构造方法,如下所示。
public FutureTask(Callable<V> callable) {
if (callable == null)
throw new NullPointerException();
this.callable = callable;
this.state = NEW;
}
public FutureTask(Runnable runnable, V result) {
this.callable = Executors.callable(runnable, result);
this.state = NEW;
}
2
3
4
5
6
7
8
9
10
11
可以看到,在FutureTask类中,提供了两个构造方法,一个是传递Callable对象,一个是传递Runnable对象和一个用于接收结果数据的泛型类型对象result。
同时,通过FutureTask类的构造方法,还可以看出,在FutureTask类存在一个Callable成员变量,通过Callable可以获取到任务的执行结果,那FutureTask类对象是如何通过Callable获取任务结果数据,并保存在自身对象中的呢?来到FutureTask的run()方法,如下所示。
public void run() {
if (state != NEW ||
!UNSAFE.compareAndSwapObject(this, runnerOffset,
null, Thread.currentThread()))
return;
try {
Callable<V> c = callable;
if (c != null && state == NEW) {
V result;
boolean ran;
try {
result = c.call();
ran = true;
} catch (Throwable ex) {
result = null;
ran = false;
setException(ex);
}
if (ran)
set(result);
}
} finally {
// runner must be non-null until state is settled to
// prevent concurrent calls to run()
runner = null;
// state must be re-read after nulling runner to prevent
// leaked interrupts
int s = state;
if (s >= INTERRUPTING)
handlePossibleCancellationInterrupt(s);
}
}
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
可以看到,在FutureTask类的run()方法中,会将成员变量callable赋值给一个局部变量c,如下所示。
Callable<V> c = callable;
如果c变量不为空,并且当前FutureTask类对象的状态还是NEW,则调用c对象的call()方法获取结果,如下所示。
result = c.call();
这里,会将结果数据保存到result变量中,随后将ran变量的值设置为true。如果在调用c对象的call()方法的过程中,捕获到异常,则将result变量设置为null,将ran变量设置为false,并且调用setException()方法设置异常信息,此时ran的值为false,不会执行set(result)代码。如果在调用c对象的call()方法的过程中,未捕获到异常,则此时ran的值为true,会执行set(result)方法将结果数据保存到FutureTask对象中,如下所示。
# 查看全文
加入冰河技术 (opens new window)知识星球,解锁完整技术文章与完整代码