欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

全面掌握同步、回调、异步回调的使用

程序员文章站 2022-06-12 19:59:00
...

本文主要解决如何理解同步调用、回调、异步调用的概念及其相互关系,然后从实例的角度来阐述它们的使用方法。

我们知道,软件众多模块之间如果要进行通信,都必须通过相互的接口进行调用,那么如何调用就成了一个问题,现如今主要存在以下三种方式的调用:

  • 同步阻塞调用
  • 回调
  • 异步回调

一、同步调用

关于同步调用有以下几个基本认知:

  • 它是一种阻塞型的调用方式,调用方总是要等到被调用方执行结束或者返回结果后才能继续往下执行;
  • 它是一种单向的调用,只存在调用方调用被调用方的行为。

同步调用很好理解,下面是一个最基本的示例:

public class Student {
    public void doHomework(){
        System.out.println("student——homework");
    }
}
public class Teacher {  
    public void giveLecture(){
        System.out.println("teacher——lecture");
    }   
    public void assignHomework(Student rose){
        System.out.println("teacher——assign——homework");
        // 同步调用
        rose.doHomework();
    }   
    public void fixHomework(){
        System.out.println("teacher——fix——homework");
    }
}
public class Test {
    public static void main(String[] args) {
        Teacher jack = new Teacher();
        Student rose = new Student();
        jack.giveLecture();
        jack.assignHomework(rose);
        jack.fixHomework();
    }
}

在上述代码中,teacher主动调用了studentdoHomework方法,只有等到该方法执行完成以后,teacher的其它行为才会执行,代码的执行结果如下:

teacher——lecture
teacher——assign——homework
student——homework
teacher——fix——homework

二、回调

关于同步调用有以下几个基本认知:

  • 回调也是一种阻塞型的调用方式,调用方也总是要等到被调用方执行结束或者返回结果后才能继续往下执行;
  • 回调是一种双向调用的模式,被调用方也可以调用调用方。

基于上面的例子,我们来通过代码看一下回调的实现逻辑:

public class Student {
    public void doHomework(Teacher teacher){
        System.out.println("student——homework");
        // 回调teacher中的方法
        teacher.fixHomework();
    }
}
public class Teacher {
    public void giveLecture(){
        System.out.println("teacher——lecture");
    }
    public void assignHomework(Student rose){
        System.out.println("teacher——assign——homework");
        // 调用student的方法时,将自身的引用也传递过去,以实现回调
        rose.doHomework(this);
    }
    public void fixHomework(){
        System.out.println("teacher——fix——homework");
    }
    public void sayGoodbye(){
        System.out.println("teacher——goodbye!");
    }
}
public class Test {
    public static void main(String[] args) {
        Teacher jack = new Teacher();
        Student rose = new Student();
        jack.giveLecture();
        jack.assignHomework(rose);
        jack.sayGoodbye();
    }
}

在上面的例子中,teacher在调用student的方法时,将自身的引用对象也传递过去了,这样student中的该方法就可以在自己认为合适的时候回调teacher中的某个方法,这就是回调,代码的执行结果如下:

teacher——lecture
teacher——assign——homework
student——homework
teacher——fix——homework
teacher——goodbye!

在本例中,使用回调的好处非常明显,teacherfixHomework不需要在main中主动调用,当studentdoHomework结束的时候自动就可以执行了。

三、异步回调

在上面回调的例子中,我们可以看到,它其实还是同步阻塞式的,也就意味着,studentdoHomework没有结束,teacher的后续行为都无法进行,这在异步调用中就可以解决这一问题。

我们还是使用上面的例子,只不过异步的实现需要借助Java的Thread来实现,具体的示例如下:

public class Student {
    public void doHomework(Teacher teacher){
        System.out.println("student——homework");
        // 回调teacher中的方法
        teacher.fixHomework();
    }
}
public class Teacher {
    public void giveLecture(){
        System.out.println("teacher——lecture");
    }
    // 备注一:这里为什么要使用final?
    public void assignHomework(final Student rose){
        System.out.println("teacher——assign——homework");
        // 启动一个线程来实现异步调用
        new Thread(
            new Runnable(){
                @Override
                public void run() {
                    // 备注二:这里为什么要使用Teacher.this而不是this?
                    rose.doHomework(Teacher.this);
                }   
            }
                ).start();
    }
    public void fixHomework(){
        System.out.println("teacher——fix——homework");
    }
    public void watchTv(){
        System.out.println("teacher——watchTv");
    }
}
public class Test {
    public static void main(String[] args) {
        Teacher jack = new Teacher();
        Student rose = new Student();
        jack.giveLecture();
        jack.assignHomework(rose);
        jack.watchTv();
    }
}

在这个例子中,teacher在assignHomework的时候启动了线程来实现异步地调用student的doHomework,然后不管doHomework是否完成都继续往下执行,然后student的doHomework完成以后才回调,这就实现了异步回调,代码的执行结果如下:

teacher——lecture
teacher——assign——homework
teacher——watchTv
student——homework
teacher——fix——homework

最后要需要对代码中的备注进行说明:

  • 备注一:其实上面例子中用到的线程实例其实是一个匿名内部类,在该匿名内部类中使用外部类方法中的形参需要把该形参定义为final类型;
  • 备注二:同样是基于匿名内部类的原因,此处使用this指代的将是当前的线程对象,而不是外部类teacher的实例引用对象,所以我们需要使用Teacher.this来强制使用外部类的实例引用对象传递给student,从而实现回调。

关于内部类的详细说明和用法,在另一篇文章《内部类有哪些?它们存在的意义是什么?》中有非常详细的介绍。