Java并发编程-扩展可回调的Future
前提
最近在看JUC线程池java.util.concurrent.ThreadPoolExecutor
的源码实现,其中了解到java.util.concurrent.Future
的实现原理。从目前java.util.concurrent.Future
的实现来看,虽然实现了异步提交任务,但是任务结果的获取过程需要主动调用Future#get()
或者Future#get(long timeout, TimeUnit unit)
,而前者是阻塞的,后者在异步任务执行时间不确定的情况下有可能需要进行轮询,这两种情况和异步调用的初衷有点相违背。于是笔者想结合目前了解到的Future
实现原理的前提下扩展出支持(监听)回调的Future
,思路上参考了Guava
增强的ListenableFuture
。本文编写的时候使用的JDK是JDK11,其他版本可能不适合。
简单分析Future的实现原理
虚拟例子推演
并发大师Doug Lea在设计JUC线程池的时候,提供了一个顶层执行器接口Executor
:
public interface Executor {
void execute(Runnable command);
}
实际上,这里定义的方法Executor#execute()
是整套线程池体系最核心的接口,也就是ThreadPoolExecutor
定义的核心线程、额外创建的线程(线程池最大线程容量 - 核心线程数)都是在这个接口提交任务的时候懒创建的,也就是说ExecutorService
接口扩展的功能都是基于Executor#execute()
的基础进行扩展。Executor#execute()
方法只是单纯地把任务实例Runnable
对象投放到线程池中分配合适的线程执行,但是由于方法返回值是void
类型,我们是无法感知任务什么时候执行完毕。这个时候就需要对Runnable
任务实例进行包装(下面是伪代码 + 伪逻辑):
// 下面这个Wrapper和Status类是笔者虚构出来
@RequiredArgsConstructor
class Wrapper implements Runnable{
private final Runnable target;
private Status status = Status.of("初始化");
@Override
public void run(){
try{
target.run();
status = Status.of("执行成功");
}catch(Throwable t){
status = Status.of("执行异常");
}
}
}
我们只需要把new Wrapper(原始Runnable实例)
投放到线程池执行,那么通过定义好的Status
状态记录变量就能得知异步任务执行的状态,以及什么时候执行完毕(包括正常的执行完毕和异常的执行完毕)。这里仅仅解决了任务执行的状态获取,但是Executor#execute()
方法法返回值是void
类型的特点使得我们无法回调Runnable
对象执行的结果。这个时候需要定义一个可以回调执行结果的接口,其实已经有现成的接口Callable
:
@FunctionalInterface
public interface Callable<V> {
V call() throws Exception;
}
这里遇到了一个问题:由于Executor#execute()
只接收Runnable
参数,我们需要把Callable
接口适配到Runnable
接口,这个时候,做一次简单的委托即可:
@RequiredArgsConstructor
class Wrapper implements Runnable{
private final Callable callable;
private Status status = Status.of("初始化");
@Getter
private Object outcome;
@Override
public void run(){
try{
outcome = callable.call();
status = Status.of("执行成功");
}catch(Throwable t){
status = Status.of("执行异常");
outcome = t;
}
}
}
这里把Callable
实例直接委托给Wrapper
,而Wrapper
实现了Runnable
接口,执行结果直接存放在定义好的Object
类型的对象outcome
中即可。当我们感知到执行状态已经结束,就可以从outcome
中提取到执行结果。
Future的实现
上面一个小结仅仅对Future
实现做一个相对合理的虚拟推演,实际上,RunnableFuture
才是JUC中常用的复合接口,它同时实现了Runnable
和Future
:
public interface RunnableFuture<V> extends Runnable, Future<V> {
void run();
}
上一节提到的虚构出来的Wrapper
类,在JUC中类似的实现是java.util.concurrent.FutureTask
,它就是Callable
和Runnable
的适配器,FutureTask
实现了RunnableFuture
接口:
public class FutureTask<V> implements RunnableFuture<V> {
private volatile int state;
private static final int NEW = 0;
private static final int COMPLETING = 1;
private static final int NORMAL = 2;
private static final int EXCEPTIONAL = 3;
private static final int CANCELLED = 4;
private static final int INTERRUPTING = 5;
private static final int INTERRUPTED = 6;
/** The underlying callable; nulled out after running */
private Callable<V> callable;
/** The result to return or exception to throw from get() */
private Object outcome; // non-volatile, protected by state reads/writes
/** The thread running the callable; CASed during run() */
private volatile Thread runner;
/** Treiber stack of waiting threads */
private volatile WaitNode waiters;
// 省略其他代码
}
注意到核心属性state
表示执行状态,outcome
承载执行结果。接着看提交Callable
类型任务的方法ExecutorService#submit()
:
public interface ExecutorService extends Executor {
// 省略其他接口方法
<T> Future<T> submit(Callable<T> task);
}
当我们通过上述ExecutorService#submit()
方法提交Callable
类型任务的时候,实际上做了如下的步骤:
- 检查入参
task
的存在性,如果为null
抛出NullPointerException
。 - 把
Callable
类型的task
包装为FutureTask
实例。 - 把新建的
FutureTask
实例放到线程池中执行,也就是调用Executor#execute(FutureTask实例)
。 - 返回
FutureTask
实例的接口实例RunnableFuture
(实际上是返回子接口Future
实例)。
如果我们需要获取结果,可以Future#get()
或者Future#get(long timeout, TimeUnit unit)
获取,调用这两个方法的时候参看FutureTask
里面的方法实现,得知步骤如下:
- 如果状态
state
小于等于COMPLETING(1)
,说明任务还在执行中,获取结果的请求线程会放入WaitNode
类型的队列中进行阻塞。 - 如果任务执行完毕,不管异常完毕还是正常完毕,除了会更新状态
state
和把结果赋值到outcome
之外,还会唤醒所有阻塞获取结果的线程,然后调用钩子方法FutureTask#done()
(具体见源码FutureTask#finishCompletion()
)。
其实分析了这么多,笔者想指出的结论就是:Callable
类型任务提交到线程池中执行完毕(包括正常执行完毕和异常执行完毕)之后,都会回调钩子方法FutureTask#done()
。这个就是我们扩展可监听Future
的理论依据。
扩展可回调的Future
先做一次编码实现,再简单测试其功能。
编码实现
先定义一个Future
接口的子接口ListenableFuture
,用于添加可监听的回调:
public interface ListenableFuture<V> extends Future<V> {
void addCallback(ListenableFutureCallback<V> callback, Executor executor);
}
ListenableFutureCallback
是一个函数式回调接口:
@FunctionalInterface
public interface ListenableFutureCallback<V> {
void callback(V value, Throwable throwable);
}
对于ListenableFutureCallback
而言,回调的结果value
和throwable
是互斥的。正常执行完毕的情况下value
将会是执行结果值,throwable
为null
;异常执行完毕的情况下,value
将会是null
,throwable
将会是抛出的异常实例。如果更习惯于分开处理正常执行完毕的结果和异常执行完毕的结果,ListenableFutureCallback
可以这样定义:
public interface ListenableFutureCallback<V> {
void onSuccess(V value);
void onError(Throwable throwable);
}
接着定义ListenableExecutorService
接口继承ExecutorService
接口:
public interface ListenableExecutorService extends ExecutorService {
<T> ListenableFuture<T> listenableSubmit(Callable<T> callable);
/**
* 定义这个方法是因为有些时候由于任务执行时间非常短,有可能通过返回的ListenableFuture实例添加回调之前已经执行完毕,因此可以支持显式传入回调
*
* @param callable callable
* @param callbacks callbacks
* @param executor executor
* @return ListenableFuture
*/
<T> ListenableFuture<T> listenableSubmit(Callable<T> callable, List<ListenableFutureCallback<T>> callbacks, Executor executor);
}
然后添加一个执行单元适配器ListenableFutureCallbackRunnable
,承载每次回调触发的调用(实现Runnable
接口,从而支持异步执行):
@RequiredArgsConstructor
public class ListenableFutureCallbackRunnable<V> implements Runnable {
private final ListenableFutureCallback<V> callback;
private final V value;
private final Throwable throwable;
@Override
public void run() {
callback.callback(value, throwable);
}
}
接着需要定义一个FutureTask
的子类ListenableFutureTask
,核心逻辑是覆盖FutureTask#done()
方法触发回调:
// ListenableFutureTask
public class ListenableFutureTask<V> extends FutureTask<V> implements ListenableFuture<V> {
private final List<Execution<V>> executions = new ArrayList<>();
public ListenableFutureTask(Callable<V> callable) {
super(callable);
}
public ListenableFutureTask(Runnable runnable, V result) {
super(runnable, result);
}
public static <V> ListenableFutureTask<V> newTaskFor(Callable<V> callable) {
return new ListenableFutureTask<>(callable);
}
@Override
protected void done() {
Iterator<Execution<V>> iterator = executions.iterator();
Throwable throwable = null;
V value = null;
try {
value = get();
} catch (Throwable t) {
throwable = t;
}
while (iterator.hasNext()) {
Execution<V> execution = iterator.next();
ListenableFutureCallbackRunnable<V> callbackRunnable = new ListenableFutureCallbackRunnable<>(execution.getCallback(),
value, throwable);
// 异步回调
if (null != execution.getExecutor()) {
execution.getExecutor().execute(callbackRunnable);
} else {
// 同步回调
callbackRunnable.run();
}
}
}
@Override
public void addCallback(ListenableFutureCallback<V> callback, Executor executor) {
Execution<V> execution = new Execution<>();
execution.setCallback(callback);
execution.setExecutor(executor);
executions.add(execution);
}
}
// Execution - 承载每个回调实例和对应的Executor,Executor实例为null则进行同步回调
@Data
public class Execution <V>{
private Executor executor;
private ListenableFutureCallback<V> callback;
}
最后一步就是编写线程池ListenableThreadPoolExecutor
,继承自ThreadPoolExecutor
并且实现ListenableExecutorService
接口:
public class ListenableThreadPoolExecutor extends ThreadPoolExecutor implements ListenableExecutorService {
public ListenableThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue<Runnable> workQueue) {
super(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue);
}
public ListenableThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit,
BlockingQueue<Runnable> workQueue, ThreadFactory threadFactory) {
super(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue, threadFactory);
}
public ListenableThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit,
BlockingQueue<Runnable> workQueue, RejectedExecutionHandler handler) {
super(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue, handler);
}
public ListenableThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit,
BlockingQueue<Runnable> workQueue, ThreadFactory threadFactory, RejectedExecutionHandler handler) {
super(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue, threadFactory, handler);
}
@Override
public <T> ListenableFuture<T> listenableSubmit(Callable<T> callable) {
if (null == callable) {
throw new IllegalArgumentException("callable");
}
ListenableFutureTask<T> listenableFutureTask = ListenableFutureTask.newTaskFor(callable);
execute(listenableFutureTask);
return listenableFutureTask;
}
@Override
public <T> ListenableFuture<T> listenableSubmit(Callable<T> callable, List<ListenableFutureCallback<T>> callbacks, Executor executor) {
if (null == callable) {
throw new IllegalArgumentException("callable");
}
if (null == callbacks) {
throw new IllegalArgumentException("callbacks");
}
ListenableFutureTask<T> listenableFutureTask = ListenableFutureTask.newTaskFor(callable);
for (ListenableFutureCallback<T> callback : callbacks) {
listenableFutureTask.addCallback(callback, executor);
}
execute(listenableFutureTask);
return listenableFutureTask;
}
}
测试
引入junit
,编写测试类如下:
public class ListenableFutureTest {
private static ListenableExecutorService EXECUTOR;
private static Executor E;
@BeforeClass
public static void before() {
EXECUTOR = new ListenableThreadPoolExecutor(1, 3, 0, TimeUnit.SECONDS,
new ArrayBlockingQueue<>(10), new ThreadFactory() {
private final AtomicInteger counter = new AtomicInteger();
@Override
public Thread newThread(Runnable r) {
Thread thread = new Thread(r);
thread.setDaemon(true);
thread.setName(String.format("ListenableWorker-%d", counter.getAndIncrement()));
return thread;
}
});
E = Executors.newFixedThreadPool(3);
}
@Test
public void testListenableFuture1() throws Exception {
ListenableFuture<String> future = EXECUTOR.listenableSubmit(() -> {
Thread.sleep(1000);
return "message";
});
future.addCallback((v, t) -> {
System.out.println(String.format("Value = %s,Throwable = %s", v, t));
}, null);
Thread.sleep(2000);
}
@Test
public void testListenableFuture2() throws Exception {
ListenableFuture<String> future = EXECUTOR.listenableSubmit(() -> {
Thread.sleep(1000);
throw new RuntimeException("exception");
});
future.addCallback((v, t) -> {
System.out.println(String.format("Value = %s,Throwable = %s", v, t));
}, null);
Thread.sleep(2000);
}
@Test
public void testListenableFuture3() throws Exception {
ListenableFuture<String> future = EXECUTOR.listenableSubmit(() -> {
Thread.sleep(1000);
return "message";
});
future.addCallback((v, t) -> {
System.out.println(String.format("Value = %s,Throwable = %s", v, t));
}, E);
System.out.println("testListenableFuture3 end...");
Thread.sleep(2000);
}
@Test
public void testListenableFuture4() throws Exception {
ListenableFuture<String> future = EXECUTOR.listenableSubmit(() -> {
Thread.sleep(1000);
throw new RuntimeException("exception");
});
future.addCallback((v, t) -> {
System.out.println(String.format("Value = %s,Throwable = %s", v, t));
}, E);
System.out.println("testListenableFuture4 end...");
Thread.sleep(2000);
}
}
执行结果:
// testListenableFuture1
Value = message,Throwable = null
// testListenableFuture2
Value = null,Throwable = java.util.concurrent.ExecutionException: java.lang.RuntimeException: exception
// testListenableFuture3
testListenableFuture3 end...
Value = message,Throwable = null
// testListenableFuture4
testListenableFuture4 end...
Value = null,Throwable = java.util.concurrent.ExecutionException: java.lang.RuntimeException: exception
和预期的结果一致,注意一下如果Callable
执行抛出异常,异常被包装为ExecutionException
,要调用Throwable#getCause()
才能得到原始的异常实例。
小结
本文通过了解ThreadPoolExecutor
和Future
的实现原理做简单的扩展,使得异步提交任务变得更加优雅和简便。强化了动手能力的同时,也能加深对并发编程的一些认知。当然,本文只是提供一个十分简陋的实现,笔者其实还想到了如对回调处理的耗时做监控、回调打上分组标签执行等等更完善的功能,等到有需要的场景再进行实现。
这里记录一下过程中的一些领悟:
Executor#execute()
是线程池的核心接口,所有其他功能都是基于此接口做扩展,它的设计本身是无状态的。- 灵活使用适配器模式,可以在不改变已发布的接口的功能同时实现新的接口的功能适配。
- 要善于发掘和使用JDK类库设计者留给开发者的扩展接口。
个人博客
(本文完 c-1-d e-a-20190702)
Java并发编程-扩展可回调的Future的更多相关文章
- Java并发编程扩展(线程通信、线程池)
之前我说过,实现多线程的方式有4种,但是之前的文章中,我只介绍了两种,那么下面这两种,可以了解了解,不懂没关系. 之前的文章-->Java并发编程之多线程 使用ExecutorService.C ...
- java并发编程 Executor,Executors,ExecutorService,CompletionService,Future,C
使用CompletionService获取多线程返回值 CompletionService和ExecutorCompletionService详解 Java并发编程系列之十五:Executor框架
- Java并发编程核心方法与框架-Future和Callable的使用
Callable接口与Runnable接口对比的主要优点是Callable接口可以通过Future获取返回值.但是Future接口调用get()方法取得结果时是阻塞的,如果调用Future对象的get ...
- Java并发编程之线程创建和启动(Thread、Runnable、Callable和Future)
这一系列的文章暂不涉及Java多线程开发中的底层原理以及JMM.JVM部分的解析(将另文总结),主要关注实际编码中Java并发编程的核心知识点和应知应会部分. 说在前面,Java并发编程的实质,是线程 ...
- Java并发编程:Callable、Future和FutureTask
作者:海子 出处:http://www.cnblogs.com/dolphin0520/ 本博客中未标明转载的文章归作者海子和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置 ...
- java并发编程--Runnable Callable及Future
1.Runnable Runnable是个接口,使用很简单: 1. 实现该接口并重写run方法 2. 利用该类的对象创建线程 3. 线程启动时就会自动调用该对象的run方法 通常在开发中结合Execu ...
- (转)Java并发编程:Callable、Future和FutureTask
Java并发编程:Callable.Future和FutureTask 在前面的文章中我们讲述了创建线程的2种方式,一种是直接继承Thread,另外一种就是实现Runnable接口. 这2种方式都有一 ...
- Java 并发编程:Callable和Future
项目中经常有些任务需要异步(提交到线程池中)去执行,而主线程往往需要知道异步执行产生的结果,这时我们要怎么做呢?用runnable是无法实现的,我们需要用callable实现. import java ...
- Java并发编程:Callable、Future和FutureTask(转)
Java并发编程:Callable.Future和FutureTask 在前面的文章中我们讲述了创建线程的2种方式,一种是直接继承Thread,另外一种就是实现Runnable接口. 这2种方式都有一 ...
随机推荐
- Qt5学习(1)
1. In Qt, if you want to apply styles to the main window itself, you must apply it to its central ...
- BeanUtils 如何拷贝 List?
BeanUtils 如何拷贝 List? 一.背景 我们在DO.Model.VO层数据间可能经常转换数据: Entity对应的是持久层数据结构(一般是数据库表的映射模型); Model 对应的是业务层 ...
- 三、JVM之方法区
一.什么式方法区 方法区,也称非堆(Non-Heap),又是一个被线程共享的内存区域.其中主要存储加载的类字节码.class/method/field等元数据对象.static-final常量.sta ...
- 【笔记】java并发编程实战
线程带来的问题:a)安全性问题b)活跃性问题c)性能问题 要编写线程安全的代码其核心在于要对状态访问操作进行管理,特别是对共享的和可变的状态的访问 Java中的主要同步机制是关键字synchroniz ...
- python3函数进阶
1.命名空间和作用域 命名空间 加载 内置命名空间 python解释器自带的变量和函数 开启python解释器自动加载内置命名空 ...
- 【python系统学习07】一张图看懂字典并学会操作
点击跳转 - 原文地址 数据类型 - 字典(dict) 目录: 一张图get字典 字典是什么 js的对象 字典长啥样 语法伪代码 示例demo 语法成像 字典怎么用 字典长度获取--len函数 提取字 ...
- 一图胜千言elasticsearch(lucene)的内存管理
- Browser Security-同源策略、伪URL的域
同源策略 同源策略的文档模型 同源策略(Same Origin policy,SOP),也称为单源策略(Single Origin policy),它是一种用于Web浏览器编程语言(如JavaScri ...
- http轮询,长轮询
轮询,长轮询 轮询 轮询:客户端定时向服务器发送Ajax请求,服务器接到请求后马上返回响应信息并关闭连接. 优点:后端程序编写比较容易. 缺点:请求中有大半是无用,浪费带宽和服务器资源. 实例:适于小 ...
- mac系统安装、启动与关闭redis
一.下载 打开官网:https://redis.io/ Download---Stable---Download5.0.4,下载最新稳定版,看具体情况而定我这里是5.0.4版本. 二.安装 下载完成后 ...