多线程-回调

参考:wenzhi的博客_峥嵘life_CSDN博客-android,java,UI领域博主

(7条消息) Java 线程回调_KunQian_smile的博客-CSDN博客_java线程回调

简单介绍下背景,为什么会突然想到去学回调相关的,虽然之前不会,但是也没有想到去看

最近换了一个项目组,我之前是做OA系统的,总的来说没什么难度,就是各种形式的CRUD。新项目组代码难度直接提升了一个量级,自研自封装通信协议,自研框架,fix通信协议与自研协议转换以及各个系统交互。

其中有一个点,据负责人说,线程回调相当普遍,我之前看博客的方向主要是锁,有个大佬告诉我,实际项目中细粒度的锁使用并不普遍,加上当前项目使用回调很多,决定先了解下回调的相关概念

开始之前,先简单了解下什么是回调,回调能做到什么样的效果。

所谓回调,就是客户程序C调用服务程序S中的某个方法A,然后S又在某个时候反过来调用C中的某个方法B,对于C来说,这个B便叫做回调方法。

既然有程序间的方法互调,那么,程序间的线程也就能实现通信了

举个例子,你老婆问你到底有多少私房钱,然后你不知道,然后你老婆气呼呼的去做别的事了,告诉你,她的微信号码,并威胁你说如果想清楚了,就用微信告诉她你到底有多少私房钱,然后你被迫去查看自己有多少钱,并如实汇报。

写上一个示例代码,展示下这种效果。

package com.demo.callback;
//你,继承了线程类,你不是一个渣男,因为你会干活
public class You extends Thread {
    private Map param;

    public int work() {
        //做你自己的工作
       System.err.println("你用工作逃避你老婆对私房钱的逼问");
    }
 
    @Override
    public void run() {
        //回答你老婆的问题,崽种
       answer();
    }
 
   
    public static interface Weixin{
       public void call(Map param);
    }
 
    // 回调接口的对象
    Weixin weixin;
 

    private void askquestion() {
       System.err.println("挣扎!犹豫!");
       try {
            //你挣扎了很久,有足足5秒
           sleep(5000);
       } catch (InterruptedException e) {
           e.printStackTrace();
       }
        //你老婆丢给你的微信确实可以打通(创建了实例)
       if (weixin!=null) {
           //你大彻大悟,决定告诉你老婆你的全部家当
           callPhone.call(param);
       }     
    }
}
package com.demo.callback;
import com.demo.callback.You.Weixin;
public class MainClass {
    //main线程,模拟你老婆的活动

    public static void main(String[] args) {
       You you = new You();
       System.out.println("你老婆问你到底有多少私房钱");
        //你用工作麻痹自己,不想承认自己藏了10块钱
       you.work();

      
       // 实例化微信对象,你老婆已经把微信给你了,告诉你自己看着办
       Weixin weixin = new Weixin () {
           @Override
           public void call(Map param) {
              //实现具体的微信对象的功能,
              param.put("answer","对不起,老婆,我藏了10块零花钱");
           }
       };
      
       //把回调对象赋值给回答者的回调对象,(你老婆吧微信给你了)
       you.weixin = weixin;
       Map param = new HashMap();
       you.setParam(param);
      //你老婆生气了,并去做自己的事情了,让你自己想明白
       System.out.println("你这个渣男,自己想明白吧!");
       //相关交代完毕之后再执行查询操作
       you.start();
      
       //你老婆满意了,此时,从map里可以获取你的私房钱情况
       System.out.println("这还差不多");
    }
 
}

流程就是,你老婆质问你并丢下微信,然后去做别的事,你做别的事不受影响,然后想通之后,拿到微信号(对象实例)并如实汇报

上面是对回调最简单的描述,其实有很多细节是值得注意的,我引用下这位大佬的博客

(7条消息) Java 线程回调_KunQian_smile的博客-CSDN博客_java线程回调

先了解和区分同步调用和异步调用的方式,从多线程的角度去理解就是阻塞式调用和非阻塞式调用

同步调用:类A中的方法a()中调用了类B中的方法b(),全过程只有一个线程,只有b()方法完全执行完毕之后,a()方法中剩下的内容才会得到执行。这是一种很基础的调用方式,适用于b()方法执行耗时不长的情况,如果b()方法执行时间很长就会造成a()方法长时间阻塞而得不到执行(也是业务中常见的超时的一种),如果b()方法本身执行时间很长,这种模式就不适用了

异步调用:这种模式是为了解决同步调用无法解决的超时问题,同样是a()方法调用b()方法,只是在a()方法中调用b()方法的时候额外启动了一个线程,这个线程执行b()方法,而a()方法自身的逻辑继续执行,不必等待b()方法执行完毕

这种方式有利有弊,好处是a()不必等待b()方法的执行,但是有这样一种业务场景-a()方法中剩余逻辑需要用到b()方法的返回结果,举个例子,我在a()方法中处理付款业务,在方法中调用外部接口方法b()查询实际付款金额,在获取到b()方法返回的金额之前,我无法继续处理付款业务,因为我不知道要付多少钱。

需要返回结果的这种场景,在异步调用中也可以通过一定手段满足,通过一定的手段对b()方法的返回进行监听,在Java中,可以使用Future+Callable的方式做到这一点

回调的思想是:

类A的a()方法调用类B的b()方法
类B的b()方法执行完毕主动调用类A的callback()方法

展示一个说明回调的代码例子

首先定义一个回调接口,在接口内部声明回调方法

/**
 * 回调接口
 */
public interface Callback {
 
    public void tellAnswer(int answer);
 
}

定义一个老师类,实现回调接口

/**
 * 老师对象
 */
public class Teacher implements Callback {
 
    private Student student;
 
    public Teacher(Student student) {
        this.student = student;
    }
 
    public void askQuestion() {
        student.resolveQuestion(this);
    }
 
    @Override
    public void tellAnswer(int answer) {
        System.out.println("知道了,你的答案是" + answer);
    }
 
}
在老师类中,我们定义了一个Student对象属性,在老师内的askQuestion()方法中,我们调用了学生类的resolveQuestion()方法,并将当前的老师对象作为参数传递到学生方法中,然后在学生方法resolveQuestion()中,我们又可以继续调用老师类的方法。

简单描述就是,老师类中有两个方法

1 askQuestion()向学生问问题

2 tellAnswer()实现的回调接口的回调方法

这里描述有点绕,建议多看两遍

接着定义一个学生接口,学生当然是解决问题,但是接收一个Callback参数,这样学生就知道解决完毕问题向谁报告:

/**
 * 学生接口,
 */
public interface Student {
 
    public void resolveQuestion(Callback callback);
 
}

然后定义具体的学生接口实现类

/**
 * 一个名叫Ricky的同学解决老师提出的问题,
 */
public class Ricky implements Student {
 
    @Override
    public void resolveQuestion(Callback callback) {
        // 模拟解决问题
        try {
            Thread.sleep(3000);
        } catch (InterruptedException e) {
 
        }
 
        // 回调,告诉老师作业写了多久
        callback.tellAnswer(3);
    }
 
}

写一个测试类,比较简单:

/**
 * 回调测试
 */
public class CallbackTest {
 
    @Test
    public void testCallback() {
        Student student = new Ricky();
        Teacher teacher = new Teacher(student);
 
        teacher.askQuestion();
 
    }
}
 

(1)将老师进行抽象

将老师进行抽象之后,对于学生来说,就不需要关心到底是哪位老师询问我问题,只要我根据询问的问题,得出答案,然后告诉提问的老师就可以了,即使老师换了一茬又一茬,对我学生而言都是没有任何影响的。

(2)将学生进行抽象

将学生进行抽象之后,对于老师这边来说就非常灵活,因为老师未必对一个学生进行提问,可能同时对Ricky、Jack、Lucy三个学生进行提问,这样就可以将成员变量Student改为List,这样在提问的时候遍历Student列表进行提问,然后得到每个学生的回答即可。

这个例子是一个典型的体现接口作用的例子,之所以这么说是因为我想到有些朋友可能不太明白接口的好处,不太明白接口好处的朋友可以重点看一下这个例子,多多理解。

总结起来,回调的核心就是回调方将本身即this传递给调用方,这样调用方就可以在调用完毕之后告诉回调方它想要知道的信息。回调是一种思想、是一种机制,至于具体如何实现,如何通过代码将回调实现得优雅、实现得可扩展性比较高,一看开发者的个人水平,二看开发者对业务的理解程度。
 

基于上面提出的,关于同步调用和异步调用的思想,对于回调,应该也存在同步回调和异步回调。

那么,基于上面的例子,是需要使用同步回调还是异步回调呢?

同步回调与异步回调

上面的例子,可能有人会提出这样的疑问:

这个例子需要用什么回调啊,使用同步调用的方式,学生对象回答完毕问题之后直接把回答的答案返回给老师对象不就好了?

这个问题的提出没有任何问题,可以从两个角度去理解这个问题。

首先,老师不仅仅想要得到学生的答案怎么办?可能这个老师是个更喜欢听学生解题思路的老师,在得到学生的答案之前,老师更想先知道学生姓名和学生的解题思路,当然有些人可以说,那我可以定义一个对象,里面加上学生的姓名和解题思路不就好了。这个说法在我看来有两个问题:

(1)如果老师想要的数据越来越多,那么返回的对象得越来越大,而使用回调则可以进行数据分离,将一批数据放在回调方法中进行处理,至于哪些数据依具体业务而定,如果需要增加返回参数,直接在回调方法中增加即可

(2)无法解决老师希望得到学生姓名、学生解题思路先于学生回答的答案的问题

因此我认为简单的返回某个结果确实没有必要使用回调而可以直接使用同步调用,但是如果有多种数据需要处理且数据有主次之分,使用回调会是一种更加合适的选择,优先处理的数据放在回调方法中先处理掉。

另外一个理解的角度则更加重要,就是标题说的同步回调和异步回调了。例子是一个同步回调的例子,意思是老师向Ricky问问题,Ricky给出答案,老师问下一个同学,得到答案之后继续问下一个同学,这是一种正常的场景,但是如果我把场景改一下:

老师并不想One-By-One这样提问,而是同时向Ricky、Mike、Lucy、Bruce、Kate五位同学提问,让同学们自己思考,哪位同学思考好了就直接告诉老师答案即可。

这种场景相当于是说,同学思考完毕完毕问题要有一个办法告诉老师,有两个解决方案:

(1)使用Future+Callable的方式,等待异步线程执行结果,这相当于就是同步调用的一种变种,因为其本质还是方法返回一个结果,即学生的回答

(2)使用异步回调,同学回答完毕问题,调用回调接口方法告诉老师答案即可。由于老师对象被抽象成了Callback接口,因此这种做法的扩展性非常好,就像之前说的,即使老师换了换了一茬又一茬,对于同学来说,只关心的是调用Callback接口回传必要的信息即可

原文链接:https://blog.csdn.net/qq_24892029/article/details/70255035

你可能感兴趣的:(java基础,多线程应用,java)