Title: 浅析Java回调机制
Date: 2018-12-27 12:30
Category: 技术博客
Modified: 2018-12-27 12:30
Tags:Callback, 设计模式
Slug: Callback
Authors: Victor Lv
Summary: 生活中,我们经常碰到这样的例子:当我们想完成某个工作时,期间会有一个非常耗时的子任务,但我们又不想干等待它的完成才继续工作,于是我们希望把这个子任务交付出去给别人完成,然后我们就可以继续往下做其他事情了,等别人完成工作的时候,又会自动回来我这告诉我完成情况。
生活中,我们经常碰到这样的例子:当我们想完成某个工作时,期间会有一个非常耗时的子任务,但我们又不想干等待它的完成才继续工作,于是我们希望把这个子任务交付出去给别人完成,然后我们就可以继续往下做其他事情了,等别人完成工作的时候,又会自动回来我这告诉我完成情况。
好比如你住酒店,你在打扫房间的时候,有一堆衣服要洗,但洗衣服这事太耗时了,所以你把清洁阿姨叫过来让她帮你拿衣服去洗,并告诉她衣服洗完后帮我放回A201房间的柜子里,托付完毕,你可以继续干手头的打扫工作了。
基于这样的思想,应用于编程中,那就衍生了回调。在应用系统中,我们经常碰到很耗时的 I/O 子任务(包括磁盘I/O、网络I/O),如果我们完全照顺序执行的,那不得不等待这个 I/O 操作完成了我们才能继续往下执行程序,那在这段 I/O 时间内 CPU 是被极度闲置的。
于是,我们很自然地想到了异步,另起一个线程做那个 I/O 操作不就行了么?但是,我们又增加了一个需求,那就是当这个 I/O 操作完成后,我希望它能告诉我,好让我继续去做某些事情,因为这些事情必须在这个 I/O 操作完成后才能执行。于是,就产生了异步回调。
下面结合程序示例来讲一讲**(异步)回调**。在讲异步回调之前,先来看几个更简单些的概念:“同步调用”、“异步调用”、“同步回调”。
同步调用
程序示例:
A.java
package callback;
/**
* @ClassName: A
* @Description: TODO
* @Author: Victor Lv
* @Date: 2018/12/27 9:27
* @Version: 1.0
*/
public class A implements InterfaceA{
private InterfaceB b;
public A(InterfaceB b) {
this.b = b;
}
public void work() {
System.out.println("Begin work");
//call b to help me do something
b.handle();
continueWorking();
}
public void continueWorking() {
System.out.println("Continue to work");
}
}
B.java
package callback;
/**
* @ClassName: B
* @Description: TODO
* @Author: Victor Lv
* @Date: 2018/12/27 9:27
* @Version: 1.0
*/
public class B implements InterfaceB {
public void handle() {
//do something
System.out.println("B takes long long time to do something");
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
这是一个很简单的示例,作用类似于:老板A在办公室干活(work),他有一封信要寄出去,但他不想自己跑腿,然后他交代他的秘书B去处理(handle),等B处理完之后他就继续工作了(continueWorking)。
这就是一个同步调用。
顺便,我们丰富了下功能,就是把老板A和秘书B接口化(面向接口编程),这样的话,秘书B即便换了人,老板还是同样的差使动作;反之,即便老板换了又换,秘书B也能干相同的差事。
InterfaceA.java
package callback;
public interface InterfaceA {
}
InterfaceB.java**
package callback;
public interface InterfaceB {
public void handle();
}
测试一下输出:
package callback;
/**
* @ClassName: CallbackTest
* @Description: TODO
* @Author: Victor Lv
* @Date: 2018/12/27 9:27
* @Version: 1.0
*/
public class CallbackTest {
public static void main(String[] args) {
B b = new B();
A a = new A(b);
a.work();
}
/**
* Output:
Begin work
B takes long long time to do something
Continue to work
*/
}
异步调用
注意上面的同步调用中,老板A必须等待秘书B处理完之后才能继续往下工作,
因为大家是在同一个线程,顺序执行的,
这就导致了老板A在秘书B寄信期间不能干活。
但是寄信这事老板并不是老板继续往下工作的必要前提,所以我们可以采用异步调用的方式处理。
异步调用相比同步调用,增加一个多线程处理即可:
只需要改下 A.java 即可:
package callback;
/**
* @ClassName: A
* @Description: TODO
* @Author: Victor Lv
* @Date: 2018/12/27 9:27
* @Version: 1.0
*/
public class A implements InterfaceA, Runnable{
private InterfaceB b;
public A(InterfaceB b) {
this.b = b;
}
public void work() {
System.out.println("Begin work");
//start another thread to handle
new Thread(this).start();
continueWorking();
}
public void continueWorking() {
System.out.println("Continue to work");
}
public void run() {
//call b to help me do something
b.handle();
}
}
测试输出:
Begin work
Continue to work
B takes long long time to do something
通过输出,我们知道,在把寄信的任务托付给了秘书B之后,老板A马上就继续工作了,
而过了一段时间B才完成了寄信的工作。
同步回调
顾名思义,同步回调相比同步调用增加了一个回调机制,来看看程序怎么实现的:
A.java
package callback;
/**
* @ClassName: A
* @Description: TODO
* @Author: Victor Lv
* @Date: 2018/12/27 9:27
* @Version: 1.0
*/
public class A implements InterfaceA{
private InterfaceB b;
public A(InterfaceB b) {
this.b = b;
}
public void work() {
System.out.println("Begin work");
//call b to help me do something
b.handle(this);
System.out.println("End work");
}
public void continueWorking() {
System.out.println("Continue to work");
}
public void callback() {
System.out.println("B callback");
continueWorking();
}
}
B.java
package callback;
/**
* @ClassName: B
* @Description: TODO
* @Author: Victor Lv
* @Date: 2018/12/27 9:27
* @Version: 1.0
*/
public class B implements InterfaceB {
public void handle(InterfaceA a) {
//do something
System.out.println("B takes long long time to do something");
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
a.callback();
}
}
InterfaceA.java
package callback;
public interface InterfaceA {
public void callback();
}
InterfaceB
package callback;
public interface InterfaceB {
public void handle(InterfaceA a);
}
测试输出
Begin work
B takes long long time to do something
B callback
Continue to work
End work
这个示例,作用类似于:老板A在办公室干活(work),
这时他感觉饿了,想吃楼下的山东煎饼,但他不想自己跑腿,
然后他交代他的秘书B去处理(handle),并告诉B处理完之后敲下我的门通知我(callback),
在B做完事回来之前,他都可以躺在椅子上休息(CPU空闲),等到B把煎饼买回来并给到他之后,他吃完又有力气可以继续干活了(continueWorking)。同步回调和同步调用一样,所有动作都是按顺序执行的,也就是类似如下的程序执行:
System.out.println("Begin work");
System.out.println("B takes long long time to do something");
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("Continue to work");
在程序中,在调用 B 进行 handle 的时候,A 需要把 this 也就是自身对象的引用传给B,
这就相当于我们住酒店时把房门钥匙给了清洁阿姨,
然后阿姨洗完衣服后通过房门钥匙开了门并把洗完的衣服放回桌上。
问题来了,房门钥匙都给了别人,那岂不是有被盗风险?
B 通过this不就可以随意调用 A 对象的 public 方法了吗?
别担心,A 和 B 都尽在程序设计者的掌控当中。因为,请注意,
B 是通过接口 InterfaceA 的方式来操作this调用回调函数的,
也就是说只有我们在 InterfaceA 定义的方法,B 才有权访问,
看看我们在 InterfaceA 定义了哪些方法?对的,只有 callback 这一种方法,
也就是说我们赋予清洁阿姨的权限只有把衣服放回桌上这一操作。
这就是一个同步回调。
异步回调
介绍完上面三个概念之后,很自然地就可以引出我们今天的主角:异步回调。
上面同步回调的例子中,老板A在秘书B把煎饼买回来之前是没法继续干活的,
因为他实在太饿了。但是哪一天老板A没那么饿呢,他想在秘书B买煎饼期间又继续干活去,
那怎么办,这就是异步回调的用武之地了。
异步回调无非就是在上面的同步回调的基础上增加异步调用嘛。来看程序示例:
A.java
package callback;
/**
* @ClassName: A
* @Description: TODO
* @Author: Victor Lv
* @Date: 2018/12/27 9:27
* @Version: 1.0
*/
public class A implements InterfaceA, Runnable{
private InterfaceB b;
public A(InterfaceB b) {
this.b = b;
}
public void work() {
System.out.println("Begin work");
//start another thread to handle
new Thread(this).start();
System.out.println("Let's take a dance");
}
public void continueWorking() {
System.out.println("Continue to work");
}
public void callback() {
System.out.println("B callback");
continueWorking();
}
public void run() {
//call b to help me do something
b.handle(this);
}
}
InterfaceA.java
package callback;
public interface InterfaceA {
public void callback();
}
测试输出
Begin work
Let's take a dance
B takes long long time to do something
B callback
Continue to work
可以看到,因为我们启用了一个新的线程让 B 去 handle,
所以不影响 A 继续干别的活,比如老板 A 想跳支舞自嗨下(take a dance)。
参考文章: