什么是异步回调?
本质就是多线程中的线程通信。随着业务模块的拆分,各个系统的业务架构变得越来越复杂,一个业务会调用很多的外部接口,为了提高效率,这种调用是异步的调用,但是最后我们需要使用返回的结果进行处理,这里就需要同步的处理结果,如何实现呢?就产生了异步回调问题。
常用场景:两个pc发送消息,一个Pc发送完了,等待另一方的response,那么避免阻塞,使用异步的方式,当结果到达的时候,通知我,kafka中的Future就是基于的这个!!
一,join异步阻塞
普通线程通信方式:使用线程阻塞,join()方法完成线程通信。使用喝茶为例子,两个事情,一个是烧水,一个是洗杯子,我们先把水烧上,火打开,然后就去洗杯子,最后,我们要确保水烧开了,准备倒水,这个时候我们要确保被子洗好了。!
public class JoinDemo {
public static void main(String []args) throws InterruptedException {
System.out.println("main-准备烧水了....");
Thread a=new Thread(new Runnable() {
public void run() {
try {
System.out.println("A-洗好水壶");Thread.sleep(1000);
System.out.println("A-倒好水");Thread.sleep(1000);
System.out.println("A-放在火上");Thread.sleep(5000);
System.out.println("A-水烧开了!。。滴滴");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});a.start();
Thread b=new Thread(new Runnable() {
public void run() {
try {
System.out.println("B-洗杯子");Thread.sleep(500);
System.out.println("B-拿茶叶");Thread.sleep(500);
System.out.println("B-洗洗洗");Thread.sleep(3000);
System.out.println("B-洗好了!");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});b.start();
a.join();
System.out.println("main-水准备好了,等待杯子");
b.join();
System.out.println("main-水 and 杯子 都准备好了,开始泡茶");
}
}
二,FutureTask
为了获取异步线程返回的结果,java在1.5之后提供了一种新的多线程创建方式:FutureTask方式。最为重要的就是FutureTask类和Callable接口。
1.Callable接口
返回值的,所以Runnable接口是不能应用于有返回值的场景,为了解决这个返回值问题,java定义了新的和Runnable类似的接口---Callable接口,并且将其中处理业务的方法命名为call,它具有返回值。
public interface Callable<V> {
V call() throws Exception;
}
设计的绝妙,利用泛型,来定义返回类型V。
与Runnable的不同点是,Runnable可以作为Thread的参数进行运行启动,但是Callable是不可以的,它需要借助一个桥梁:FutureTask类才能与Thread配合使用。
2.FutureTask类
从意思理解:未来指向的任务。表示新线程要执行的任务,它将Callable封装起来,然后又继承了Runnable接口,所以可以作为Thread的参数进行启动。(这里Future出现了)
FutureTask类的两个构造方法:
//传入 callable接口
public FutureTask(Callable<V> callable) {
this.callable = callable;
}
//传入runnable接口,
public FutureTask(Runnable runnable, V result) {
this.callable = Executors.callable(runnable, result);
}
3.Future接口
将FutureTask的一系列操作抽象成为了接口,也就是Future接口,
主要提供的三大功能:
1.判断并发任务是否执行完成
2.获取并发的任务完成后的结果
3.取消并发执行中的任务
public interface Future<V> {
boolean cancel(boolean mayInterruptIfRunning);
boolean isCancelled();
boolean isDone();
V get() throws InterruptedException, ExecutionException;
V get(long timeout, TimeUnit unit)
throws InterruptedException, ExecutionException, TimeoutException;
}
接口说明:get()获取并发执行任务的结果,方法是阻塞的,如果并发任务没有完成,则会等待。
isDone();获取并发任务的执行状态,任务执行结束,返回true;
isCancelled();获取并发任务的取消状态,如果完成前被取消,则返回true;
cancel():取消并发任务的执行;
4.FutureTask类
有成员callable<V>,业务逻辑放在call()方法中,run()方法调用call()方法,然后执行完call()将结果保存起来,保存到outcome属性中,它是Object。然后通过get()来获取。
FutureTask.run()
public void run() {
...
Callable<V> c = callable;
if (c != null && state == NEW) {
V result;
boolean ran;
try {
result = c.call(); //调用方法
ran = true;
} ...
if (ran)
set(result); //设置结果
}
}
...
}
喝茶demo:
public class JoinDemo {
static class HotJob implements Callable<Boolean>{
public Boolean call()throws Exception{
System.out.println("A-洗好水壶");Thread.sleep(1000);
System.out.println("A-倒好水");Thread.sleep(1000);
System.out.println("A-放在火上");Thread.sleep(5000);
System.out.println("A-水烧开了!。。滴滴");
return true;
}
}
static class WashJob implements Callable<Boolean>{
public Boolean call()throws Exception{
System.out.println("B-洗杯子");Thread.sleep(500);
System.out.println("B-拿茶叶");Thread.sleep(500);
System.out.println("B-洗洗洗");Thread.sleep(3000);
System.out.println("B-洗好了!");
return true;
}
}
public static void main(String []args) throws InterruptedException, ExecutionException {
Callable<Boolean> hjob=new HotJob();
//构建FutureTask类
FutureTask<Boolean> htask=new FutureTask<>(hjob);
//因为实现了Runnable接口,所以作为参数传入,用于启动
Thread a=new Thread(htask);
Callable<Boolean> wjob=new WashJob();
FutureTask<Boolean> wTask=new FutureTask<>(wjob);
Thread b=new Thread(wTask);
System.out.println("main-准备烧水了....");
//启动两个线程
a.start();b.start();
if(htask.get()) //获得run()结果值
System.out.println("main-水准备好了,等待杯子");
if(wTask.get())
b.join();System.out.println("main-水 and 杯子 都准备好了,开始泡茶");
}
}
缺点:阻塞get结果,效率有点低。
三,Guava的异步回调
它是谷歌对FutureTask的扩展升级,提供了异步回调的解决方案,它主要就是增强了java的异步回调,实现了非阻塞的获取结果。(也就是FutureTask.get()方法)
1.引入了新的接口ListenableFutre,继承了Future接口,使得Future异步任务,在Guava中能被监控和获得非阻塞异步执行的结果。
2.引入了新的接口FutureCallback,该接口的目的:是在异步任务执行完成后,根进异步结果,完成不同的回调处理,并且可以处理异步结果。
1.FutureCallback
public interface FutureCallback<V> {
//在异步任务执行成功后被回调,调用时,异步任务的执行结果,作为该方法的参数被传入。
void onSuccess(@Nullable V var1);
//异步任务执行过程中,抛出异常时被回调,抛出的异常作为参数。
void onFailure(Throwable var1);
}
Callable和FutureCallback的区别,前者代表的是异步任务逻辑,后者代表的是对异步任务(callable的call()方法)结果的处理,分为成功or异常两种情况进行处理。
2.ListenableFuture
public interface ListenableFuture<V> extends Future<V> {
void addListener(Runnable var1, Executor var2);
}
它也只是对Futre的扩展,而且addListener()方法只是在Guava内部调用,在实际编程中,不会调用这个方法,它的作用是将上面的FutureCallback善后回调工作封装成内部的Runnable异步回调任务。理解为异步任务的实例。
如何把FutureCallback回调逻辑,绑定到异步的ListenableFuture任务呢?
可以使用Futures工具类,它有一个addCallback静态方法,可以将回调逻辑绑定到异步任务中。(也就是把两个作为参数,传入进去)
public static <V> void addCallback(ListenableFuture<V> future, FutureCallback<? super V> callback) {
addCallback(future, callback, MoreExecutors.directExecutor());
}
Futures这个类,有点复杂。
3.ListenableFuture异步任务
如何获取异步任务实例呢?(注意这里全是接口,所以是没办法使用的,需要实例来操作)
主要是通过向线程池提交Callable任务的方式来获得,这个线程池是guava对java线程池的定制。
任务提交之后的返回结果,就是我们需要的ListenableFuture异步任务实例了。
public class GuavaDemo {
private static boolean warter;
private static boolean cup;
static {
warter=false;cup=false;
}
static class HotJOb implements Callable<Boolean>{
public Boolean call()throws Exception{
。。。
}
}
static class WashJOb implements Callable<Boolean>{
。。。
}
public static void main(String args[]) throws InterruptedException {
Callable<Boolean> hotjob=new HotJOb();
Callable<Boolean> wathjob=new WashJOb();
//创建java线程池
ExecutorService Pool= Executors.newFixedThreadPool(10);
//包装java线程池
ListeningExecutorService pool= MoreExecutors.listeningDecorator(Pool);
//提交业务逻辑实例,到guava线程池 获取异步任务-----------submit方法也就是start();
ListenableFuture<Boolean> hotFuture=pool.submit(hotjob);
//绑定异步回调,烧水完成后,把喝水任务的warterOK标志设置为true
Futures.addCallback(hotFuture, new FutureCallback<Boolean>() {
public void onSuccess(Boolean aBoolean) {
if(aBoolean)
GuavaDemo.warter=true;
}
public void onFailure(Throwable throwable) {
System.out.print("烧水失败");
}
});
ListenableFuture<Boolean> washFuture=pool.submit(wathjob);
Futures.addCallback(washFuture, new FutureCallback<Boolean>() {
public void onSuccess(Boolean aBoolean) {
if(aBoolean)
GuavaDemo.cup=true;
}
public void onFailure(Throwable throwable) {
System.out.print("洗杯子失败");
}
});
//主线程
while(true){
Thread.sleep(1000);
if(GuavaDemo.cup&&GuavaDemo.warter){
System.out.print(" 茶泡好 OK");
break;}
}
}
}
总结:这里我们设置了标志位,不需要我们手动去访问这个任务的返回结果了,前面是在get()返回值的时候,进行阻塞,然后再进行业务处理,而此处,对返回结果再次进行了整合。
给我的感觉就是线程通信问题。
四,Netty的异步回调
kafka上运用的超级多!总体上是它的扩展:
类名字是一样的,包的位置不同,
第二个:引入了一个新接口,GenericFutureListener,由于表示异步执行完成的监听器。它和FutureCallback接口不同,netty使用监听器的模式。它与原jdk的异步回调相比,就是多了监听器,实现了非阻塞获取返回值,淘汰了futureTask.get()阻塞方法。
1.详解GenericFutureListener接口
它对应guava的futureCallback接口,用来封装异步非阻塞回调的逻辑。
public interface GenericFutureListener<F extends Future<?>> extends EventListener {
// 监听器的回调方法
void operationComplete(F var1) throws Exception;
}
2.Future接口
public interface Future<V> extends java.util.concurrent.Future<V> {
// 监听器调用调用这个方法来进行判断,然后执行对应的逻辑
boolean isSuccess(); //判断异步执行是否成功
boolean isCancellable(); //判断异步执行是否取消
Throwable cause(); //获取异步任务异常的原因。
//增加异步任务执行完成与否的监听器。
Future<V> addListener(GenericFutureListener<? extends Future<? super V>> var1);
Future<V> addListeners(GenericFutureListener... var1);
//移除监听器
Future<V> removeListener(GenericFutureListener<? extends Future<? super V>> var1);
Future<V> removeListeners(GenericFutureListener... var1);
Future<V> sync() throws InterruptedException;
Future<V> syncUninterruptibly();
Future<V> await() throws InterruptedException;
Future<V> awaitUninterruptibly();
boolean await(long var1, TimeUnit var3) throws InterruptedException;
boolean await(long var1) throws InterruptedException;
boolean awaitUninterruptibly(long var1, TimeUnit var3);
boolean awaitUninterruptibly(long var1);
V getNow();
boolean cancel(boolean var1);
}
3.ChannelFuture接口
netty网络编程中,网络连接通道的输入/输出都是异步的,都会返回一个channelFuture接口的实例,通过这个实例,可以为它增加异步回调的监听器。(就是返回成功怎么怎么处理)kafka上有很多例子!,send()方法,返回一个ChannelFuture,然后下面马上给这个future添加一个监听器,lister.
kafka中的demo
ChannelFuture joinFuture = sendJoinGroupRequest();
joinFuture.addListener(new RequestFutureListener<ByteBuffer>() {
public void onSuccess(ByteBuffer value) {
synchronized (AbstractCoordinator.this) {
state = MemberState.STABLE;
rejoinNeeded = false;
if (heartbeatThread != null)
heartbeatThread.enable();
}
}
public void onFailure(RuntimeException e) {
synchronized (AbstractCoordinator.this) {
state = MemberState.UNJOINED;
}
}
});