两个例子了解java中的回调机制
作者:程序新视界 发布时间:2023-07-12 21:12:46
目录
前言
系统调用的分类
同步回调实例
异步回调实例
基于Future的半异步
小结
前言
先让我们通过一个生活中的场景来还原一下回调的场景:你遇到了一个技术难题(比如,1+1等于几?太难了!),于是你去咨询大牛,大牛说现在正在忙,待会儿告诉你结果。
此时,你可能会去刷朋友圈了,等大牛忙完之后,告诉你答案是2。
那么,这个过程中询问问题(调用对方接口),然后问题解决之后再告诉你(对方处理完再调用你,通知结果),这一过程便是回调。
系统调用的分类
应用系统模块之间的调用,通常分为:同步调用,异步调用,回调。
同步调用是最基本的调用方式。类A的a()方法调用类B的b()方法,类A的方法需要等到B类的方法执行完成才会继续执行。如果B的方法长时间阻塞,就会导致A类方法无法正常执行下去。
如果A调用B,B的执行时间比较长,那么就需要考虑进行异步处理,使得B的执行不影响A。通常在A中新起一个线程用来调用B,然后A中的代码继续执行。
异步通常分两种情况:第一,不需要调用结果,直接调用即可,比如发送消息通知;第二,需要异步调用结果,在Java中可使用Future+Callable实现。
通过上图我们可以看到回到属于一种双向的调用方式。回调的基本上思路是:A调用B,B处理完之后再调用A提供的回调方法(通常为callbakc())通知结果。
通常回调分为:同步回调和异步回调。网络上大多数的回调案例都是同步回调。
其中同步回调与同步调用类似,代码运行到某一个位置的时候,如果遇到了需要回调的代码,会在这里等待,等待回调结果返回后再继续执行。
而异步回调与异步调用类似,代码执行到需要回调的代码的时候,并不会停下来,而是继续执行,当然可能过一会回调的结果会返回回来。
同步回调实例
下面我们以同步回调为例来讲解回调的Java代码实现。整个过程就模拟上面问答问题的场景。
首先,定义给一个CallBack的接口,将回调的功能进行单独抽离:
public interface CallBack {
void callback(String string);
}
CallBack接口中提供了一个callback方法,用于回调时调用。
然后定义问问题的人Person:
public class Person implements CallBack {
private Genius genius;
public Person(Genius genius) {
this.genius = genius;
}
@Override
public void callback(String string) {
System.out.println("收到答案:" + string);
}
public void ask() {
genius.answer(this);
}
}
由于Person要提供回调方法,因此实现CallBack接口及其方法,方法中主要针对回调结果进行处理。
同时,由于Person要调用Genius对应的方法,因此要持有Genius的引用,这里通过构造方法传入。
定义回答问题的大神Genius类:
public class Genius {
public void answer(CallBack callBack) {
System.out.println("在忙其他事...");
try {
Thread.sleep(2000);
System.out.println("忙完其他事,开始计算...");
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("天才计算出答案为:2");
// 回调告诉你
callBack.callback("2");
}
}
这模拟大神正在忙碌,线程睡眠2秒,忙碌完之后,开始帮忙计算答案,获得答案之后,调用CallBack接口的callback方法进行回调,通知结果。
通过Main方法进行测试:
public static void main(String[] args) {
Genius genius = new Genius();
Person you = new Person(genius);
you.ask();
}
执行打印结果如下:
在忙其他事...
忙完其他事,开始计算...
天才计算出答案为:2
收到答案:2
上面的过程,就实现了一个同步回调的功能。当然,从程序设计上来说,可以对Person和Genius进一步抽象化处理,通过接口的形式呈现。
在上述回调机制的代码实现中,最核心的是在调用answer方法时传递了this参数,即调用者自身。
从本质上来说,回调是一种思想,是一种机制,至于具体如何实现,如何通过代码将回调实现得优雅、实现得可扩展性比较高,就需要八仙过海各显神通了。
异步回调实例
上面的实例演示了同步回调,很明显在调用的过受到Genius执行时长的影响,需要等到Genius处理完才能继续执行Person方法中的后续代码。
下面在上述示例上进行改进,Person提供一个支持异步回调的方法:
public void askASyn() {
System.out.println("创建新线程请教问题");
new Thread(() -> genius.answer(this)).start();
System.out.println("新线程已启动...");
}
在该方法内,新建了一个线程用来处理Genius#answer方法的调用,这样就能够跳过Genius#answer方法的阻塞,直接执行下面的操作(日志打印)。
在main方法中将调用的方法改为askASyn,打印结果如下:
创建新线程请教问题
新线程已启动...
在忙其他事...
忙完其他事,开始计算...
天才计算出答案为:2
收到答案:2
可以看出,直接打印了“新线程已启动...”,后续才打印出Genius#answer方法方法中处理日志和回调时callback方法接收到的信息。
基于Future的半异步
除了上述的同步,异步处理,还有一种介于同步和异步之间的基于Future的半异步处理。
在Java使用nio后无法立即拿到真实的数据,而是先得到一个"future",可以理解为邮戳或快递单,为了获悉真正的数据我们需要不停的通过快递单号"future"查询快递是否真正寄到。
Futures是一个抽象的概念,它表示一个值,在某一点会变得可用。一个Future要么获得计算完的结果,要么获得计算失败后的异常。
通常什么时候会用到Future呢?一般来说,当执行一个耗时的任务时,使用Future就可以让线程暂时去处理其他的任务,等长任务执行完毕再返回其结果。
经常会使用到Future的场景有:1. 计算密集场景。2. 处理大数据量。3. 远程方法调用等。
Java在java.util.concurrent包中附带了Future接口,它使用Executor异步执行。
例如下面的代码,每传递一个Runnable对象到ExecutorService.submit()方法就会得到一个回调的Future,使用它检测是否执行,这种方法可以是同步等待线处理结果完成。
public class TestFuture {
public static void main(String[] args) {
//实现一个Callable接口
Callable<User> c = () -> {
//这里是业务逻辑处理
//让当前线程阻塞1秒看下效果
Thread.sleep(1000);
return new User("张三");
};
ExecutorService es = Executors.newFixedThreadPool(2);
// 记得要用submit,执行Callable对象
Future<User> fn = es.submit(c);
// 一定要调用这个方法,不然executorService.isTerminated()永远不为true
es.shutdown();
// 无限循环等待任务处理完毕 如果已经处理完毕 isDone返回true
while (!fn.isDone()) {
try {
//处理完毕后返回的结果
User nt = fn.get();
System.out.println(nt.name);
} catch (InterruptedException | ExecutionException e) {
e.printStackTrace();
}
}
}
static class User {
private String name;
private User(String name) {
this.name = name;
}
}
}
此种情况下虽然是创建了新线程来进行处理,但还是需要等待处理的结果。好处是可以将批量的处理,分为几个线程同时进行处理,最后对结果进行合并,达到提升处理效率的目的。
小结
经过这篇文章,想必大家对Java的回调机制已经有所了解,在各类开源框架中,其实也会经常看到回调的使用,活学活用。
来源:https://mp.weixin.qq.com/s/2-XGeVHn972YNFCyvK137Q


猜你喜欢
- 具体不做详细介绍了,直接上代码/// <summary> /// 功能:FileStream文件流读取文件 &nbs
- 使用视图引擎可以完成一些需要定制化内容格式的问题,比如邮件模板。引用install-package RazorEngine使用public
- Spring Cloud Feign简介 Spring Cloud Feign也是一个基础工具类,它整合了Spring Cloud Ribb
- 一、配置xml路径mybatis-plus:mapper-locations: classpath:mapper/*.xml二、编写Mapp
- 自动注入和@Autowire@Autowire不属于自动注入!注入方式(重要)在Spring官网上(文档),定义了在Spring中的注入方式
- java web返回中文乱码ajax返回中文乱码问题 在浏览器按F12查看数据包可以看到charset为 iso-8859-1,这是spri
- Spring中实现多线程,其实非常简单,只需要在配置类中添加@EnableAsync就可以使用多线程。在希望执行的并发方法中使用@Async
- 今天讲解一下Fragment的控制,主要是切换View和页面替换等操作。还有就是如何获取Fragment的管理对象,以及与Activity的
- 本文实例为大家分享了Android自定义View实现滑动回弹的具体代码,供大家参考,具体内容如下前言Android 页面滑动的时候的回弹效果
- 本文实例讲述了C#设置输入法的方法。分享给大家供大家参考。具体如下:private void Form1_Load(object sende
- 一次性全部绘制出来实现代码import java.awt.*;public class AlgoVisualizer {private st
- 在 C 语言中,如果发生错误,上级函数要进行出错处理,层层上传,容易造成过多的出错处理代码,并且传递的效率比较低下。C++ 中的异常C++
- 什么是EJB?EJB 是 Java 企业Bean, 是JavaEE服务端 企业组件模型,它的设计目标与核心应用是部署分布式应用程序。话不多说
- 发一个库存程序,好像是几个礼拜之前写的吧,是一个用安卓实现的简易的计算器,写这个小程序之前,看了很多人写的计算器,觉得使用一个 EditTe
- G将军有一支训练有素的军队,这个军队除开G将军外,每名士兵都有一个直接上级(可能是其他士兵,也可能是G将军)。现在G将军将接受一个特别的任务
- 具体实现方式不多说了,请看下文一、前言当下微信公众号几乎已经是每个公司必备的,但是大部分微信公众账号用户体验都欠佳,特别是涉及到用户绑定等,
- Android中Toolbar随着ScrollView滑动透明度渐变效果实现一.思路:监听ScrollView的滑动事件 不断的修改Tool
- Android系统支持的颜色是由4个值组成的,前3个为RGB,也就是我们常说的三原色(红、绿、蓝),最后一个值是A,也就是Alpha。这4个
- Mybatis是业界非常流行的持久层框架,轻量级、易用,在金融IT领域完全是领军地位,比Hibernate更受欢迎,优势非常多,也是非常值得
- 一、前言 Android 中解决滑动的方案有2种:外部拦截法 和内部拦截法。 滑动冲突也存在2种场景: 横竖滑动冲突、同向滑动冲突。 所以我