如何用Java回调和线程实现异步调用

Python023

如何用Java回调和线程实现异步调用,第1张

软件模块之间的调用关系可以分为两大类:即同步调用和异步调用。在同步调用中,一段代码(主调方)调用另一段代码(被调方),主调方必须等待这段代码执行完成返回结果后,才能继续往下执行,所以,同步调用是一种阻塞式调用,主调方代码一直阻塞等待直到被调方返回为止。同步调用相对比较直观,也是大部分编程语言直接支持的一种调用方式。但是,同步调用在处理比较耗时的情况下会严重影响程序性能,影响人机交互的瞬时反应。例如,某个程序需要访问数据库获取大量数据,然后根据这些数据进行一系列处理,将处理结果显示在程序主窗口。由于数据库访问和大量数据的处理都是耗时的工作,在这个工作完成之前,处理结果迟迟不能显示,用户点击鼠标也不会立即得到响应,让用户感到整个程序显得很沉重。面对这样一些需要比较长时间才能完成的应用场景,我们需要采用一种非阻塞式调用方式,即异步调用方式。在异步调用中,主调方调用被调方后,不等待对方返回结果就继续执行后续代码,被调方执行完毕后,通过某种手段通知调用方:结果已经出来,请酌情处理。我们可以对上面的例子改用异步调用将问题轻松化解:把整个耗时的工作放进一个单独的线程,由主调方启动此线程后继续执行后续代码,线程在背后悄悄地处理费时的工作,当工作完成,采用回调的方式通知主调方工作完成,主调方将结果显示在主窗口。经过这样的处理,主界面继续进行自己的工作而不必死等,就不会造成界面响应迟钝。

在实现异步调用机制时,除了线程之外,还要用到回调。回调是一种双向调用,也就是,被调方在被调用时也会调用主调方的代码。在异步调用中,被调方需要在工作完成时通知主调方,即调用主调方的接口,这一机制通过回调实现。回调和异步调用的关系非常紧密,回调是异步调用的基础[1]。

本文理论联系实际,首先阐述如何使用Java实现回调机制,然后进一步阐述使用Java回调和线程实现异步调用,最后,阐述在异步调用中如何处理超时问题。

1 Java回调机制的实现方法

实现Java回调,需要做如下三件事情:

(1)定义一个回调接口CallbackInterface

接口中声明回调方法handle,如图1所示,回调方法就是一个普通的方法,接收一个消息字符串或者一个封装了数据的事件。

(2)定义一个类实现回调接口

这个类其实就是消息接收者和处理者,也就是调用方,回调方法是消息发生时实际处理消息的方法,此处简化为一条打印语句。

(3)定义消息通知者

消息通知者也就是被调用方必须具备两种能力,第一,它必须知道谁是消息接收者,第二,当消息发生时,它能够回调这些接收者的回调方法。为了获得这两种能力,消息通知者首先必须提供一个注册方法register, 通过注册的方式来注册多个对此消息或事件感兴趣的对象。然后提供一个消息通知方法notifyMessage,在这个方法中调用所有消息接收者的回调方法。具体代码如图3所示。

比如用一个可变数组List用于保存消息接收者,注册的过程实际上是将消息接收者添加到这个数组,以备在需要通知消息的时候调用这些消息接收者的回调方法。

使用Java回调和线程实现异步调用

线程是一个独立的执行流,其本质是程序中一段并发执行的代码。在异步调用机制中引入线程,在线程中完成耗时的工作,其目的是让调用方的主线程继续执行后续代码而不需要等待被调方的结果返回。由于不需要等待,这样我们就等于同时做了两件事情,而这两件事情分别是在不同的执行流中执行,主调者在当前的主线程中执行,被调者在另外一个线程中执行,因此提高了程序的效率,避免了界面的响应迟钝。当被调者执行完成后,仍然采用回调通知主调者。

例如 LongTimeWorker是一个用于完成耗时工作的线程,同时又是消息通知者。其耗时工作在run方法中完成,另外提供一个注册方法register, 和一个消息通知方法notifyMessage,在run方法的最后,即耗时工作完成以后,调用notifyMessage将消息广播出去。

3 异步调用中超时问题的处理

异步调用通常都要加入超时机制,因为我们总是希望在一个指定的时间范围内返回一个结果,即使没有得到结果也该有个超时通知。这时我们需要使用“限时线程回调方式”,它在原有线程回调的基础上加上一个计时器Timer以计算消耗的时间,如果时间期限到了任务还没有执行完成即中断线程,并将超时消息广播出去。LongTimeWorker类需要修改部分的代码如图8和图9所示。

首先LongTimeWorker线程类增加了一个构造方法,其参数是超时时间timeout,构造方法的主要任务是创建一个定时器,每秒钟计时一次,若超时时间到则终止本线程,并广播超时消息。LongTimeWorker线程类的第二个改变发生在其run方法中,线程一启动立即开始计时,完成工作后停止计时,并广播消息。

4 结束语

异步调用是一种非阻塞式调用方式,用于在处理比较耗时的任务时保证程序性能不受到影响。实现异步调用的关键在于要解决三个技术难题,它们分别是程序阻塞问题、异步消息的传递问题和超时问题。本文介绍的方法采用并发线程、回调机制和计时器使上述问题得到了圆满解决。

如果主线程要等子线程完成才能继续执行又何必新开线程如果你非要这么干,java.util.concurrent.Future接口满足你的需求,具体用法百度

Future 接口

Future 接口允许表示已经完成的任务、正在执行过程中的任务或者尚未开始执行的任务。通过 Future 接口,可以尝试取消尚未完成的任务,查询任务已经完成还是取消了,以及提取(或等待)任务的结果值。

FutureTask 类实现了 Future,并包含一些构造函数,允许将 Runnable 或 Callable(会产生结果的 Runnable)和 Future 接口封装。因为 FutureTask 也实现 Runnable,所以可以只将 FutureTask 提供给 Executor。一些提交方法(如 ExecutorService.submit())除了提交任务之外,还将返回 Future 接口。

Future.get() 方法检索任务计算的结果(或如果任务完成,但有异常,则抛出 ExecutionException)。如果任务尚未完成,那么 Future.get() 将被阻塞,直到任务完成;如果任务已经完成,那么它将立即返回结果。

可以啊,java层可以调用native层,native层照样可以调用java层。相关资料不太好找,可以找些示例看看。但是你要关注一下回调回java层的线程问题。坑点比较多。建议你回调java主线程的方法当做子线程处理回调,再通过一些安全的方法回调主线程