更多内容请关注微信公众号【Java技术江湖】

这是一位阿里 Java 工程师的技术小站,作者黄小斜,专注 Java 相关技术:SSM、SpringBoot、MySQL、分布式、中间件、集群、Linux、网络、多线程,偶尔讲点Docker、ELK,同时也分享技术干货和学习经验,致力于Java全栈开发!(关注公众号后回复”资料“即可领取 3T 免费技术学习资源以及我我原创的程序员校招指南、Java学习指南等资源)

本文主要介绍了Java中的回调机制,以及Java多线程中类似回调的机制。

具体代码在我的GitHub中可以找到

https://github.com/h2pl/MyTech

文章首发于我的个人博客:

https://h2pl.github.io/2018/04/26/javase9

更多关于Java后端学习的内容请到我的CSDN博客上查看:https://blog.csdn.net/a724888

模块间的调用

本部分摘自https://www.cnblogs.com/xrq730/p/6424471.html

在一个应用系统中,无论使用何种语言开发,必然存在模块之间的调用,调用的方式分为几种:

(1)同步调用

同步调用是最基本并且最简单的一种调用方式,类A的方法a()调用类B的方法b(),一直等待b()方法执行完毕,a()方法继续往下走。这种调用方式适用于方法b()执行时间不长的情况,因为b()方法执行时间一长或者直接阻塞的话,a()方法的余下代码是无法执行下去的,这样会造成整个流程的阻塞。

(2)异步调用


异步调用是为了解决同步调用可能出现阻塞,导致整个流程卡住而产生的一种调用方式。类A的方法方法a()通过新起线程的方式调用类B的方法b(),代码接着直接往下执行,这样无论方法b()执行时间多久,都不会阻塞住方法a()的执行。

但是这种方式,由于方法a()不等待方法b()的执行完成,在方法a()需要方法b()执行结果的情况下(视具体业务而定,有些业务比如启异步线程发个微信通知、刷新一个缓存这种就没必要),必须通过一定的方式对方法b()的执行结果进行监听。

在Java中,可以使用Future+Callable的方式做到这一点,具体做法可以参见我的这篇文章Java多线程21:多线程下其他组件之CyclicBarrier、Callable、Future和FutureTask。

(3)回调

最后是回调,回调的思想是:

类A的a()方法调用类B的b()方法 类B的b()方法执行完毕主动调用类A的callback()方法 这样一种调用方式组成了上图,也就是一种双向的调用方式。

回调实例:Tom做题

数学老师让Tom做一道题,并且Tom做题期间数学老师不用盯着Tom,而是在玩手机,等Tom把题目做完后再把答案告诉老师。

1 数学老师需要Tom的一个引用,然后才能将题目发给Tom。

2 数学老师需要提供一个方法以便Tom做完题目以后能够将答案告诉他。

3 Tom需要数学老师的一个引用,以便Tom把答案给这位老师,而不是隔壁的体育老师。

回调接口,可以理解为老师接口

    //回调指的是A调用B来做一件事,B做完以后将结果告诉给A,这期间A可以做别的事情。
//这个接口中有一个方法,意为B做完题目后告诉A时使用的方法。
//所以我们必须提供这个接口以便让B来回调。
//回调接口,
public interface CallBack {
void tellAnswer(int res);
}

数学老师类

    //老师类实例化回调接口,即学生写完题目之后通过老师的提供的方法进行回调。
//那么学生如何调用到老师的方法呢,只要在学生类的方法中传入老师的引用即可。
//而老师需要指定学生答题,所以也要传入学生的实例。
public class Teacher implements CallBack{
private Student student; Teacher(Student student) {
this.student = student;
} void askProblem (Student student, Teacher teacher) {
//main方法是主线程运行,为了实现异步回调,这里开启一个线程来操作
new Thread(new Runnable() {
@Override
public void run() {
student.resolveProblem(teacher);
}
}).start();
//老师让学生做题以后,等待学生回答的这段时间,可以做别的事,比如玩手机.\
//而不需要同步等待,这就是回调的好处。
//当然你可以说开启一个线程让学生做题就行了,但是这样无法让学生通知老师。
//需要另外的机制去实现通知过程。
// 当然,多线程中的future和callable也可以实现数据获取的功能。
for (int i = 1;i < 4;i ++) {
System.out.println("等学生回答问题的时候老师玩了 " + i + "秒的手机");
}
} @Override
public void tellAnswer(int res) {
System.out.println("the answer is " + res);
}
}

学生接口

    //学生的接口,解决问题的方法中要传入老师的引用,否则无法完成对具体实例的回调。
//写为接口的好处就是,很多个学生都可以实现这个接口,并且老师在提问题时可以通过
//传入List<Student>来聚合学生,十分方便。
public interface Student {
void resolveProblem (Teacher teacher);
}

学生Tom

public class Tom implements Student{

    @Override
public void resolveProblem(Teacher teacher) {
try {
//学生思考了3秒后得到了答案,通过老师提供的回调方法告诉老师。
Thread.sleep(3000);
System.out.println("work out");
teacher.tellAnswer(111);
} catch (InterruptedException e) {
e.printStackTrace();
}
}

测试类

public class Test {
public static void main(String[] args) {
//测试
Student tom = new Tom();
Teacher lee = new Teacher(tom);
lee.askProblem(tom, lee);
//结果
// 等学生回答问题的时候老师玩了 1秒的手机
// 等学生回答问题的时候老师玩了 2秒的手机
// 等学生回答问题的时候老师玩了 3秒的手机
// work out
// the answer is 111
}
}

多线程中的“回调”

Java多线程中可以通过callable和future或futuretask结合来获取线程执行后的返回值。实现方法是通过get方法来调用callable的call方法获取返回值。

其实这种方法本质上不是回调,回调要求的是任务完成以后被调用者主动回调调用者的接口。而这里是调用者主动使用get方法阻塞获取返回值。

public class 多线程中的回调 {
//这里简单地使用future和callable实现了线程执行完后
public static void main(String[] args) throws ExecutionException, InterruptedException {
ExecutorService executor = Executors.newCachedThreadPool();
Future<String> future = executor.submit(new Callable<String>() {
@Override
public String call() throws Exception {
System.out.println("call");
TimeUnit.SECONDS.sleep(1);
return "str";
}
});
//手动阻塞调用get通过call方法获得返回值。
System.out.println(future.get());
//需要手动关闭,不然线程池的线程会继续执行。
executor.shutdown(); //使用futuretask同时作为线程执行单元和数据请求单元。
FutureTask<Integer> futureTask = new FutureTask(new Callable<Integer>() {
@Override
public Integer call() throws Exception {
System.out.println("dasds");
return new Random().nextInt();
}
});
new Thread(futureTask).start();
//阻塞获取返回值
System.out.println(futureTask.get());
}
@Test
public void test () {
Callable callable = new Callable() {
@Override
public Object call() throws Exception {
return null;
}
};
FutureTask futureTask = new FutureTask(callable); }
}

Java基础9:解读Java回调机制的更多相关文章

  1. Java基础16:Java多线程基础最全总结

    Java基础16:Java多线程基础最全总结 Java中的线程 Java之父对线程的定义是: 线程是一个独立执行的调用序列,同一个进程的线程在同一时刻共享一些系统资源(比如文件句柄等)也能访问同一个进 ...

  2. java基础学习总结——java环境变量配置(转)

    只为成功找方法,不为失败找借口! 永不放弃,一切皆有可能!!! java基础学习总结——java环境变量配置 前言 学习java的第一步就要搭建java的学习环境,首先是要安装 JDK,JDK安装好之 ...

  3. Java基础教程:Java内存区域

    Java基础教程:Java内存区域 运行时数据区域 Java虚拟机在执行Java程序的过程种会把它所管理的内存划分为若干个不同的数据区域.这些区域都有各自的用途,以及创建和销毁的时间,有的区域随着虚拟 ...

  4. 068 01 Android 零基础入门 01 Java基础语法 08 Java方法 06 参数传递问题——基本数据类型传值

    068 01 Android 零基础入门 01 Java基础语法 08 Java方法 06 参数传递问题--基本数据类型传值 本文知识点:参数传递问题--基本数据类型传值 说明:因为时间紧张,本人写博 ...

  5. 067 01 Android 零基础入门 01 Java基础语法 08 Java方法 05 数组作为方法参数

    067 01 Android 零基础入门 01 Java基础语法 08 Java方法 05 数组作为方法参数 本文知识点:数组作为方法参数 说明:因为时间紧张,本人写博客过程中只是对知识点的关键步骤进 ...

  6. 066 01 Android 零基础入门 01 Java基础语法 08 Java方法 02 带参有返回值方法

    066 01 Android 零基础入门 01 Java基础语法 08 Java方法 04 带参有返回值方法 本文知识点:带参有返回值方法 说明:因为时间紧张,本人写博客过程中只是对知识点的关键步骤进 ...

  7. 065 01 Android 零基础入门 01 Java基础语法 08 Java方法 02 带参无返回值方法

    065 01 Android 零基础入门 01 Java基础语法 08 Java方法 03 带参无返回值方法 本文知识点:带参无返回值方法 说明:因为时间紧张,本人写博客过程中只是对知识点的关键步骤进 ...

  8. 064 01 Android 零基础入门 01 Java基础语法 08 Java方法 02 无参带返回值方法

    064 01 Android 零基础入门 01 Java基础语法 08 Java方法 02 无参带返回值方法 本文知识点:无参带返回值方法 说明:因为时间紧张,本人写博客过程中只是对知识点的关键步骤进 ...

  9. 063 01 Android 零基础入门 01 Java基础语法 08 Java方法 01 无参无返回值方法

    063 01 Android 零基础入门 01 Java基础语法 08 Java方法 01 无参无返回值方法 本文知识点:无参无返回值方法 无参无返回值方法 案例 为什么使用方法?--方便复杂问题调用 ...

  10. 062 01 Android 零基础入门 01 Java基础语法 07 Java二维数组 01 二维数组应用

    062 01 Android 零基础入门 01 Java基础语法 07 Java二维数组 01 二维数组应用 本文知识点:二维数组应用 二维数组的声明和创建 ? 出现空指针异常 数组的名字指向数组的第 ...

随机推荐

  1. 派多个订单给一个司机,拒单是同一订单id

    问题:多次派单给一个司机,发现多个拒单请求是同一个订单id的. 原因:来单页面是SingleTask, 并且没有重写onNewIntent, 而倒计时结束拒单的时候会弹窗提示,只有点了确认按钮才会把当 ...

  2. 1. Linux系统介绍

    1. 什么是操作系统? 定义:操作系统是计算机系统中必不可少的基础系统软件,它的作用是负责管理和控制计算机系统中的硬件和软件资源,合理地组织计算机系统的工作流程,以便有效地利用资源为使用者提供一个功能 ...

  3. 20181115 python-第一章学习小结part2

    Python基本知识 变量,用来存储中间计算结果,在后面可进行调用被使用的东西,叫做变量. 变量的命名规则: 字母,数字,下划线组合 不能用数字开头 常见的关键字不能用啊 常量,不会变的量,称作常量. ...

  4. 32位二进制IP地址与十进制IP地址互相转换

    代码: import java.util.List; import java.util.ArrayList; import java.util.Scanner; public class Transf ...

  5. 算法第四版中 while (!StdIn.isEmpty()) 循环无法跳出问题

    在IDEA中使用Ctrl+D就可以退出console输入

  6. 原生JS面试题

    面试中遇到的原生js题总结   1.js 去重1) indexOf Array.prototype.unique = function(){ var result = []; var len = th ...

  7. mysql学习2

    1.视图 视图是一个虚拟表(非真实存在),其本质是[根据sql语句获取动态的数据集,并为其命名],用户使用时只需要使用[名称]即可获取结果集,并可以将其当作表来使用. 搜索临时表 SELECT * F ...

  8. 自行搭建私有云kodexplorer

    kodexplorer是一款开源的私有云框架,可以通过它实现个人网盘的功能,如果拥有一个性能不错的VPS,那么就可以摆脱奇慢无比的百度云等网盘啦!最近百度网盘还发出申明,说要限制使用空间.用别人的东西 ...

  9. 马昕璐 201771010118《面向对象程序设计(java)》第十六周学习总结

    第一部分:理论知识学习部分 程序:一段静态的代码,应用程序执行的蓝本. 进程:是程序的一次动态执行,它对应了从代码加载.执行至执行完毕的一个完整过程. 多线程:进程执行过程中产生的多条执行线索,比进程 ...

  10. Keepalived+Nginx实现高可用Web负载均衡

    1.安装编译 Nginx 所需的依赖包# yum install gcc gcc-c++ make automake autoconf libtool pcre pcre-devel zlib zli ...