北京这周终于凉快了不少,舒了一口气,但是中暑带来的后遗症还没减轻,晕晕乎乎地上了一周的班也不见好转,基本就是这状态:

程序员

好了,不扯了。上篇文章讲了回调的基本原理:调用方通过方法将自己身的实例传给被调用方,被调用方拿到实例之后,就可以将结果返回给调用方了。

不知道大家看了上一篇文章之后会不会觉得哪里怪怪的,有点蹩脚。蹩脚的原因在于,XueMei的实例里面其实有一个XueZhang的对象,那么回调的过程其实还是在一个线程里面,自己跟自己的方法打转,该等待的还是要等待啊,并没有达到让学妹不要等待的目的。

为了让大家一目了然,我们把代码修改一下:

学妹的问问题方法:

public void askQuetion(){
System.out.println("I have a question");
Date start = new Date();
xueZhang.processQuestion(this);
System.out.println((new Date().getTime() - start.getTime())/1000+"s:"+"I'v got it,then do another job");
}

学长的回答问题方法:

public void processQuestion(CallBack callBack) {
try {
Thread.sleep(10000);
} catch (InterruptedException e) {
e.printStackTrace();
}
callBack.onResult( "zhi ge hao shuai");
}

结果:

result.png

可以看到中间是间隔了10s的,学妹才去做的其他事情,其实这就是一种同步回调的方式,所以感觉起来会很蹩脚。当然我觉得同步回调有一种用处就在于:可以立马返回一个结果给调用方。

在XueZhang的代码中加一行:

public void processQuestion(CallBack callBack) {
callBack.onResult("I receive your question");
try {
Thread.sleep(10000);
} catch (InterruptedException e) {
e.printStackTrace();
}
callBack.onResult( "zhi ge hao shuai");
}

结果:

result.png

我们就可以通过这个立马返回的结果知道被调用方已经收到我的请求,我可以安心去做其他事了。

那么,真正达到可以让调用方不等待的话,那就是异步回调。其实我才想起来在学前端和Andorid的时候,会经常接触到onCreate()方法,这就是回调的运用啊。

我们修改XueZhang的代码:

public void processQuestion(CallBack callBack) {
new Thread(new Runnable() {
@Override
public void run() {
try {
Thread.sleep(10000);
} catch (InterruptedException e) {
e.printStackTrace();
}
callBack.onResult("zhi ge hao shuai");
}
}).start();
}

新起一个线程,当执行完了自己通知,于是结果就是这样了:

result3.png

看到了吧,学妹终于可以干自己的事情了,不用等那个装逼的学长的答案了。

可以的话,回顾一下我之前写的关于同步与异步,阻塞与非阻塞的关系,当时其实是有一个遗留问题的:异步操作既然是被调用方在执行结束后通知调用方,那究竟要怎么通知呢?现在相信你有了答案。

嗯,你以为就结束了吗?

不,我们可能还是在单机上转悠啊,现在做的大部分系统,都是分布式的,我们得赶紧从学校学的那种前端加后端的模式中跳出来。

先给大家开个头吧,这样理解,我们调用的服务已经从一个臃肿的垂直架构中抽离出来了,也就是服务的运行有自己的环境,你要调用服务的服务不在你本地,怎么办呢?

大概有这么几种方式:

1.RPC。这是运用广泛的一种模式。通常会借助一个服务管理中心,管理各种在线服务,如阿里的dubbo。

2.REST。我们的调用当然还可以采用网络传输的方式啊,所以restful风格的这种API就大行其道啊,当然网络其实是最不靠谱的。

3.消息队列。MQ,Kafka,这类的东西的思想就是,我把要做的任务推(Push)到topic里面,消费者自己去拉(Pull)吧。

既然这样,我们回调通常也有这种跨机器的回调模式了。

分享一段国内开源框架LTS的代码(我其实也没深看):

SubmitCallback submitCallback = new SubmitCallback() {
@Override
public void call(RemotingCommand responseCommand) {
if (responseCommand == null) {
response.setFailedJobs(jobs);
response.setSuccess(false);
response.setMsg("Submit Job failed: JobTracker is broken");
LOGGER.warn("Submit Job failed: {}, {}", jobs, "JobTracker is broken");
return;
}
if (JobProtos.ResponseCode.JOB_RECEIVE_SUCCESS.code() == responseCommand.getCode()) {
if (LOGGER.isDebugEnabled()) {
LOGGER.debug("Submit Job success: {}", jobs);
}
response.setSuccess(true);
return;
}
// 失败的job
JobSubmitResponse jobSubmitResponse = responseCommand.getBody();
response.setFailedJobs(jobSubmitResponse.getFailedJobs());
response.setSuccess(false);
response.setCode(JobProtos.ResponseCode.valueOf(responseCommand.getCode()).name());
response.setMsg("Submit Job failed: " + responseCommand.getRemark() + " " + jobSubmitResponse.getMsg());
LOGGER.warn("Submit Job failed: {}, {}, {}", jobs, responseCommand.getRemark(), jobSubmitResponse.getMsg());
}
};
if (SubmitType.ASYNC.equals(type)) {
asyncSubmit(requestCommand, submitCallback);
} else {
syncSubmit(requestCommand, submitCallback);
}

不用关注逻辑,就看结构。有个内部类,或者说是匿名内部类获取SubmitCallback的对象,之后会通过同步和异步的方式发到另外的机器上处理请求。

之后的代码太多,我就不上传了,大概是:

异步的方式会将一个CallBack的实例一直往下传,而同步的方法则是选择等待一个返回值,从方法名就可以看出来:

public RemotingCommand invokeSync(RemotingCommand request)
public void invokeAsync(RemotingCommand request, AsyncCallback asyncCallback)

一个有返回值,一个没返回值。有兴趣的自己下着看一下,我研究的也不深,而且还有一些小疑问,等解决了之后再更吧。

回调太多就会使系统耦合加深,所以在单机的项目下还是少用吧。