基础---异步回调

什么是异步回调?

    本质就是多线程中的线程通信。随着业务模块的拆分,各个系统的业务架构变得越来越复杂,一个业务会调用很多的外部接口,为了提高效率,这种调用是异步的调用,但是最后我们需要使用返回的结果进行处理,这里就需要同步的处理结果,如何实现呢?就产生了异步回调问题。

    常用场景:两个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()接口中的run()方法中,它是没有返回值的,所以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的异步回调

    它和guava一样,实现了自己的异步回调体系,继承和扩展了Jdk future系列的异步回调api。在kafka上运用的超级多!总体上是它的扩展:

      第一个:继承java的future接口,得到了一个新的future异步任务接口,对原来的进行增强,但是类名字是一样的,包的位置不同,

      第二个:引入了一个新接口,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;
                    }
                }
            });

  • 3
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值