浅析Java回调机制

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)。

参考文章:

Java回调机制解读-博客园

Java 中回调机制是什么原理?-知乎

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值