通常业务流程中会处理很多的事情,有一些是可以并行的执行的,这时候如果全部串行的执行这些业务是很耗时的,而如果使用异步调用,统一结果收集的方式将会极大的提高效率,进一步,如果需要获得这些并行业务的结果,那么通过异步回调来获取结果又是一个大的提升。
以下将从Java Future异步回调技术入手,然后介绍Guava Future,最后介绍一下Netty的异步回调技术
讲Future异步回调之前,先看看join的方式
public class ThreadA extents Thread{
public void run(){
//do something in threadA
}
}
public class ThreadB extents Thread{
public void run(){
//do something in threadB
}
}
public class MainThread{
public void main(String[] args){
ThreadA threadA = new ThreadA();
ThreadB threadB = new ThreadB();
threadA.start();
threadB.start();
threadA.join();
threadB.join();
System.out.print("main Thread finish");
}
}
通过分别调用threadA和threadB的join方法,主线程可以等待直到两个线程任务执行完。但是这种方式的问题是主线程等待期间什么都不能做。并且无法获取threadA,threadB线程的执行结果,即使两个任务线程抛出异常,主线程也不会知道
public static class ThreadA extends Thread{
@Override
public void run() {
System.out.println("aa");
}
}
public static class ThreadB extends Thread{
@SneakyThrows
@Override
public void run() {
throw new PlatformOpeException(ExceptionEnum.COMMON_SUCCESS);
}
}
public static void main(String[] args) throws InterruptedException {
ThreadA threadA = new ThreadA();
ThreadB threadB = new ThreadB();
threadA.start();
threadB.start();
threadA.join();
threadB.join();
System.out.println("main thread finish");
}
Exception in thread "Thread-1" PlatformOpeException(exceptionCode=100000, exceptionMessage=操作成功, exceptionEnum=COMMON_SUCCESS)
aa
main thread finish
at com.xiaomi.mifi.policy.admin.store.PolicyDataRecordServiceFactory$ThreadB.run(PolicyDataRecordServiceFactory.java:61)
鉴于Runnable接口无法获取线程执行状态结果,JDK1.5之后出现了FutureTask和Callable两个接口
FutureTask既然作为Callable的载体,那么他就需要一个outCome来存储Callable的返回值
使用FutureTask和Callable作为异步执行的案例代码
public class ThreadA implements Callable{
public Integer call() throws Exception{
return 1;
}
}
public class ThreadB implements Callable{
public Integer call() throws Exception{
return 2;
}
}
public static void main(String[] args){
FutureTask aTask = new FutureTask<>(new ThreadA());
Thread threadA = new Thread(aTask);
FutureTask bTask = new FutureTask<>(new ThreadB());
Thread threadB = new Thread(bTask);
threadA.start();
threadB.start();
Integer aResult = aTask.get();
Integer bResult = bTask.get();
if(aResult == 1 && bResult == 1){
System.out.print("main Thread finished here");
}
}
这里我们使用FutureTask和Callable解决了自任务状态的问题,但是对于异步执行还是和join一样,主线程需要一直阻塞,直到拿到结果。
在google的Guava中,通过ListenableFuture解决了异步调用的问题,让异步任务可以真正的“异步”,主线程可以做其他事情,直到拿到结果做依赖子任务结果的处理逻辑。
首先看看Guava中的FutureCallback接口
public interface FutureCallback{
void onSuccess(@Nullable V var1);
void onFailure(@Throwable var1);
}
通过FutureCallback我们可以在Callable任务执行完成之后,根据状态执行善后工作。
然后Guava通过ListenableFuture实现了Callable和FutureCallback结果回调之间的监控关系
public interface ListenableFuture extends Future{
void addListener(Runnable r,Executor e);
}
也可以通过Future的addCallback方法将FutureCallback回调逻辑绑定到异步的ListenableFuture任务。(ListenableFuture就是自任务的FutureTask,FutureCallback是需要监听子线程状态的回调函数,比如主线程有一个标识符表示子线程的状态,通过while循环不断的轮询这个状态,如果子线程未准备好,那么继续主线程的逻辑。这个状态在FutureCallback中会根据子线程的状态进行变更)
Futures.addCallback(listenableFuture,new FutureCallback(){
public void onSuccess(Integer result){
//.....子线程状态标识符变更
}
public void onFailure(Throwable t){
//.....子线程状态标识符变更
}
})
Guava的异步回调除了需要定义线程池以外还需要定义它自己的线程池,用来做回调使用的线程池
public class ThreadA implements Callable{
public Integer call(){
return 1;
}
}
public class ThreadB implements Callable{
public Integer call(){
return 2;
}
}
public static void main(String[] args){
int aResult = 0;
int bResult = 0;
ListenableExecutorService gPool = MoreExecutors.listeningDecorator(Executors.newFixedThreadPool(10));
ListenableFuture aFuture = gPool.submit(new ThreadA());
Futures.addCallback(aFuture,new FutureCallback(){
public void onSuccess(Integer result){
aResult = result;//这里是回调函数的实现逻辑
}
public void onFailure(Throwable t){
aResult = -1;//这里是回调函数的实现逻辑
}
});
ListenableFuture bFuture = gPool.submit(new ThreadB());
Futures.addCallback(bFuture,new FutureCallback(){
public void onSuccess(Integer result){
bResult = result;//这里是回调函数的实现逻辑
}
public void onFailure(Throwable t){
bResult = -1;//这里是回调函数的实现逻辑
}
});
while(true){
if(aResult == -1 || bResult == -1){
System.out.print("sub thread run fail");
break;
}else if(aResult == 1 && bResult == 1){
System.out.print("sub thread run success");
break;
}else{
System.out.print("sub thread not finish");
}
}
System.out.print("main thread finish");
}
Gauva 是异步非阻塞的,FutureTask是异步阻塞的。
Netty作为高性能的网络框架,它内部使用了大量的异步回调的机制,在Netty中异步回调的实现是通过GenericFutureListener来定义的(之所以是定义,是因为Netty中有很多的事件,不同的事件会有不同的FutureListener,而这些基本都是基于GenericFutureListener)
public interface GenericFutureListener> extends EventListener{
void operationComplet(F var) throws Exception;
}
Netty中有很多的事件,不同的事件有不同的Future,比如ChannelFuture用来处理IO操作的异步任务,也就是在IO操作完成后,需要执行回调操作,就需要使用ChannelFuture接口
ChannelFuture future = bootstrap.connect(new InetSocketAddress("www.baidu.com"))
future.addListener(new ChannelFutureListener(){
@Override
public void operationComplete(ChannelFuture channelFuture) throws Exception{
if(channelFuture.isSuccess()){
sysout("success");//回调逻辑
}
}
})