回调函数 异步执行

理解回调函数,java异步执行,多线程 

很多语言都有回调函数,所以必须理解它。

java的形式:传递this指针给其他线程(等对方执行完后,用this引用来拿到回调函数)、传递匿名类以及显式的Callback接口。

js中常见的ajax(我觉得这个是最容易理解的回调函数)。

Nodejs中的事件编程。

RocketMQ里面的客户端异步发消息给Broker

。。。。。。。。

好了,现在以最平凡的java为例,假设有两个函数:

函数g():         public int g(){

         return 1+1;

   }

函数f()       public void f1(){

           int l =g();

     System.out.println(l);//本例中输出2就算成功

    }

将以上代码随便写个main跑一下f1(),执行过程分析:

启动main函数时,jvm在栈区划出一块-Xss大小的内存给main线程。接下来main()调用f1()f1()需要的栈帧会在main的调用栈里面分配。同理,f1()调用g()g()也是在这个栈上分配空间。执行结束后,g弹栈àf1弹栈。整个过程都在main的栈里面发生。

 假如g是个耗时的任务,而且f()接下来的代码中暂时还不需要g的返回值,g早一点返回更好,晚一点也能接受。那就可以把g()放到另一个线程,main线程继续执行f()接下来的逻辑去。

至于g()什么时候返回、f()怎么拿到值,撅滴尅提供两种方式:

1future.get(),

2、以及第二种通常更优的方式:回调函数。

如果还有第三种,请各位大神不吝赐教啊!

无论哪一种,第一步都要先new子线程、让子线程执行g()。而f()还是在父线程调用栈里面。

 

开始动手:总共分两步:

1步、定义一个Callable,实现call,里面只调g()

//不采用匿名类方式,定义Callable线程:

private class A implements Callable{

             public Integer call() {

                   return g();

             }

}

 

2步、实例化这个Callable start()线程,并用一个futureTask.get()去等返回值。

//再到f2中实例化线程A。并等待结果。

    public void f2() throws ExecutionException, InterruptedException {

            new Thread(new FutureTask<Integer>(new A())).start();

            f.get();//这是个阻塞-轮询方法,不会让父线程结束或者干其他的去,一直等到子线程返回

System.out.println(l);//输出l就算成功

}

分析:

f2创造了一个子线程,jvm又新建一块大小为-Xss的调用栈给新线程使用,可以通过打印线程名字来查看。在这个新调用栈里执行g()。可是父线程因为执行了阻塞方法f.get(),会每隔一段时间去询问一下子线程“搞完了没”。直到子线程回答“搞完了”。

虽然满足了g()在另一个线程中执行,也获取了cpu时间片和内存资源,但是很明显,父线程在等子线程,这样岂不是很傻?如果子线程知道父线程会怎么对待它的返回值,那可以代劳啊!

所以应该用回调函数来实现我们的目的。当父线程发出命令后就可以去干别的事情了(虽然本例中没有别的事情~V~)。等到子线程自己弄完,它自己会根据回调函数的逻辑去处理返回值了。

         //第一步:定义回调函数。相当于父线程告诉子线程:“将来你执行完你自己的逻辑后,按照我给的MyCallbackonSuccess方法去处理一下结果”。

interface MyCallback{

        void onSuccess(int l);

}

//第二步:子线程的定义有点小复杂:子线程是个Callable,它的call方法中只调用g()。它要提供一个可以注入回调函数的构造器。当然也可以通过其他方式设置进来啊,不一定非要构造函数。

    private class A implements Runnable {

          private MyCallback myCallback;

          A(MyCallback myCallback){

                    this.myCallback = myCallback;

          }

         public void run() {

               int l = g();

               myCallback.onSuccess(l);//按照父线程给的逻辑来处理g()的结果

          }

}

第二步的最后需要子线程配合,要在应用层改代码,要是程序员忘了调onSuccess,那就白费功夫设计这半天啦!所以spring就很聪明,用扫描注解(@Async)的方法,再通过cglib生成一个新的class的方式尽量减少应用代码的改动。

//第三步(本例中最后一步 ):主驱动f方法。用匿名类实例化了new MyCallback,逻辑都放在onSuccess里面:当g()执行成功后,需要主动调一下onSuccess来额外处理一下结果。

    public void f3() throws ExecutionException, InterruptedException{

        Runnable a = new A(new MyCallback() {

            public void onSuccess(int l) {

                System.out.println(l);

            }

        });

        new Thread(a).start();

}

 从客户端f()的视角来看,需要多准备好一个回调函数给被调用者。注意理解一下,回调函数上如果带形参,将来需要被调用方(本例中的g方法)传入实参进来的哦!

 

回调函数才是真正的异步。jdkfuture.get()机制只做到了一大半。一些第三方框架,例如akka已经提供了回调机制的future了,而且api也很简洁。所以真正编码时并不需要我们自己写这么复杂的逻辑。

另外,名字很重要,“回调”这个叫法是因为本来是f调用g,子线程是被调用方,但却需要在最后额外执行一段调用方(本例中执行f方法的线程)提供的onSuccess()函数。它与调用的方向相反,回去了,所以onSuccess被称为回调。

引入回调函数是为了解决被调用方---这里是g()---执行任务耗时不确定的难点。就好像小明妈妈让小明去买酱油,妈妈不知道小明啥时候能回来,就嘱咐了一句(相当于给一个回调函数):“酱油买回来、放到厨房后,你才能去玩电脑”。然后小明妈妈自己忙自己的去了。小明也去买酱油了。要是用future.get(),在小明出去的这段时间内,妈妈最终(最终是因为妈妈不会在小明刚出门就开始给小明打电话,比喻父线程在子线程start()之后和future.get()之前的那些代码)会陷入每隔三分钟给小明打电话的循环中,问小明你买到了吗?一直打到小明回来后,妈妈才给小明说:“好了,酱油买回来了,你去玩电脑吧”。

最后切记、切记,回调函数一定要和多线程配合才有意义。否则,同一个线程调用栈里面,上下栈帧在那里自娱自乐,这太傻。这就好比妈妈自己去买酱油,还对自己说:“酱油买回来,要放好,才能玩电脑”。不仅没有并行,还浪费妈妈口水。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值