异步回调的 Java 实现
最编程
2024-07-16 16:15:01
...
1、什么是回调
设想一个情景,A是处理业务的一个步骤,A需要解决一个问题,这时候A可以问B,让B来告诉A答案,这期间,A可以继续做自己的事情,而不用因为B做的事而阻塞。于是,我们想到给B设置一个线程,让B去处理耗时的操作,然后处理完之后把结果告诉A。所以这个问题的要点就在于B处理完之后如何把结果告诉A。我们可以直接在A中写一个方法对B处理完的结果进行处理,然后B处理完之后调用A这个方法。这样A调用B去处理过程,B调用A的C方法去处理结果就叫做回调。
在正常的业务中使用同步线程,如果服务器每处理一个请求,就创建一个线程的话,会对服务器的资源造成浪费。因为这些线程可能会浪费时间在等待网络传输,等待数据库连接等其他事情上,真正处理业务逻辑的时间很短很短,但是其他线程在线程池满了之后又会阻塞,等待前面的线程处理完成。而且,会出现一个奇怪的现象,客户端的请求被阻塞,但是cpu的资源使用却很低,大部分线程都浪费在处理其他事情上了。所以,这就导致服务器并发量不高。而异步,则可以解决这个问题。我们可以把需要用到cpu的业务处理使用异步来实现,这样其他请求就不会被阻塞,而且cpu会保持比较高的使用率。综上,可以使用回调来实现异步的方法。
2、Java回调范例
回调接口:
public interface CallBack { /* 为什么要写这个回调接口呢? *因为可能不止主调A需要用到被调的处理过程,如果很多地方需要用到被调程序 * 那么传入被调的方法就不可能只传主调A类,所以要定义一个接口, * 传入被调的处理方法的参数就是这个接口对象 * */ public void solve(String result); }
主调程序:
public class CallbackRequest implements Callback{ private CallbackResponse callbackResponse; public CallbackRequest(CallbackResponse callbackResponse) { this.callbackResponse = callbackResponse; } //主调需要解决一个问题,所以他把问题交给被调处理,被调单独创建一个线程,不影响主调程序的运行 public void request(final String question){ System.out.println("主调程序问了一个问题"); new Thread(()->{ //B想要帮A处理东西,就必须知道谁让自己处理的,所以要传入a,也要知道a想处理什么,所以要传入question callbackResponse.handler(this, question); }).start(); //A把要处理的事情交给b之后,就可以自己去玩耍了,或者去处理其他事情 afterAsk(); } private void afterAsk(){ System.out.println("主调程序继续处理其他事情"); } @Override public void solve(String result) { System.out.println("被调程序接到答案后进行处理" + result); } }
被调程序:
public class CallbackResponse { public void handler(Callback callback, String request) { System.out.println(callback.getClass()+"问的问题是:"+ request); try { Thread.sleep(3000); } catch (InterruptedException e) { e.printStackTrace(); } String result="\n答案是2"; callback.solve(result); } }
测试:
public class CallbackTest { public static void main(String[] args) { CallbackResponse callbackResponse = new CallbackResponse(); CallbackRequest callbackRequest = new CallbackRequest(callbackResponse); callbackRequest.request("1+1"); } } 输出: 主调程序问了一个问题 主调程序继续处理其他事情 class javapratice.CallbackRequest问的问题是:1+1 被调程序接到答案后进行处理 答案是2
3、异步回调
异步回调的实现依赖于多线程或者多进程。软件模块之间总是存在着一定的接口,从调用方式上,可以把他们分为三类:同步调用、回调和异步调用。同步调用是一种阻塞式调用,调用方要等待对方执行完毕才返回,它是一种单向调用;回调是一种双向调用模式,也就是说,被调用方在接口被调用时也会调用对方的接口;异步调用是一种类似消息或事件的机制,不过它的调用方向刚好相反,接口的服务在收到某种讯息或发生某种事件时,会主动通知客户方(即调用客户方的接口)。回调和异步调用的关系非常紧密,通常我们使用回调来实现异步消息的注册,通过异步调用来实现消息的通知。
3.1、多线程中的“回调” (JDK8之前)
Java多线程中可以通过callable和future或futuretask结合来获取线程执行后的返回值。实现方法是通过get方法来调用callable的call方法获取返回值。其实这种方法本质上不是回调,回调要求的是任务完成以后被调用者主动回调调用者的接口,而这里是调用者主动使用get方法阻塞获取返回值。一般情况下,我们会结合Callable和Future一起使用,通过ExecutorService的submit方法执行Callable,并返回Future。
//多线程中的“回调” public class CallBackMultiThread { //这里简单地使用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()); } }
注:比起future.get(),其实更推荐使用get (long timeout, TimeUnit unit)方法,设置了超时时间可以防止程序无限制的等待future的结果。
上一篇: Java 实现异步的 8 种方法
下一篇: JAVA 项目中的异步任务
推荐阅读
-
Java 编程系列] 使用 Java 访问 Microsoft Graph 并实现发送电子邮件的功能。
-
高级 Java 每日面试题 - September 30, 2024 - 算法 - 什么是 LRU?如何实现?-我的回答
-
每天学习一种数据结构 - 链表 - 链表的 Java 实现
-
助农小程序 | 助农扶贫系统 | 基于 java 的助农扶贫系统小程序设计与实现(源代码 + 数据库 + 文档)
-
Java 中的五种 I/O 模式详解--五、异步 I/O(Asynchronous I/O)
-
玩了一盘Java实现的2048小游戏,竟然一次都没赢,大家觉得我这个水平如何?
-
【Java编程】用简单的100行代码实现2048小游戏
-
Java 8新特性探究(十三)JavaFX 8新特性以及开发2048游戏-JavaFX历史## 跟java在服务器端和web端成绩相比,桌面一直是java的软肋,于是Sun公司在2008年推出JavaFX,弥补桌面软件的缺陷,请看下图JavaFX一路走过来的改进 从上图看出,一开始推出时候,开发者需使用一种名为JavaFX Script的静态的、声明式的编程语言来开发JavaFX应用程序。因为JavaFX Script将会被编译为Java bytecode,程序员可以使用Java代码代替。 JavaFX 2.0之后的版本摒弃了JavaFX Script语言,而作为一个Java API来使用。因此使用JavaFX平台实现的应用程序将直接通过标准Java代码来实现。 JavaFX 2.0 包含非常丰富的 UI 控件、图形和多媒体特性用于简化可视化应用的开发,WebView可直接在应用中嵌入网页;另外 2.0 版本允许使用 FXML 进行 UI 定义,这是一个脚本化基于 XML 的标识语言。 从JDK 7u6开始,JavaFx就与JDK捆绑在一起了,JavaFX团队称,下一个版本将是8.0,目前所有的工作都已经围绕8.0库进行。这是因为JavaFX将捆绑在Java 8中,因此该团队决定跳过几个版本号,迎头赶上Java 8。 ##JavaFx8的新特性 ## ###全新现代主题:Modena 新的Modena主题来替换原来的Caspian主题。不过在Application的start方法中,可以通过setUserAgentStylesheet(STYLESHEET_CASPIAN)来继续使用Caspian主题。 参考http://fxexperience.com/2013/03/modena-theme-update/ ###JavaFX 3D 在JavaFX8中提供了3D图像处理API,包括Shape3D (Box, Cylinder, MeshView, Sphere子类),SubScene, Material, PickResult, LightBase (AmbientLight 和PointLight子类),SceneAntialiasing等。Camera类也得到了更新。从JavaDoc中可以找到更多信息。 ###富文本 强化了富文本的支持 ###TreeTableView ###日期控件DatePicker 增加日期控件 ###用于 CSS 结构的公共 API
-
Java实现大顶堆:理解大顶堆在Java中的含义
-
实现和简单分析java中的几种排序算法