扫二维码与项目经理沟通
我们在微信上24小时期待你的声音
解答本文疑问/技术咨询/运营咨询/技术建议/互联网交流
软件模块之间的调用关系可以分为两大类:即同步调用和异步调用。在同步调用中,一段代码(主调方)调用另一段代码(被调方),主调方必须等待这段代码执行完成返回结果后,才能继续往下执行,所以,同步调用是一种阻塞式调用,主调方代码一直阻塞等待直到被调方返回为止。同步调用相对比较直观,也是大部分编程语言直接支持的一种调用方式。但是,同步调用在处理比较耗时的情况下会严重影响程序性能,影响人机交互的瞬时反应。例如,某个程序需要访问数据库获取大量数据,然后根据这些数据进行一系列处理,将处理结果显示在程序主窗口。由于数据库访问和大量数据的处理都是耗时的工作,在这个工作完成之前,处理结果迟迟不能显示,用户点击鼠标也不会立即得到响应,让用户感到整个程序显得很沉重。面对这样一些需要比较长时间才能完成的应用场景,我们需要采用一种非阻塞式调用方式,即异步调用方式。在异步调用中,主调方调用被调方后,不等待对方返回结果就继续执行后续代码,被调方执行完毕后,通过某种手段通知调用方:结果已经出来,请酌情处理。我们可以对上面的例子改用异步调用将问题轻松化解:把整个耗时的工作放进一个单独的线程,由主调方启动此线程后继续执行后续代码,线程在背后悄悄地处理费时的工作,当工作完成,采用回调的方式通知主调方工作完成,主调方将结果显示在主窗口。经过这样的处理,主界面继续进行自己的工作而不必死等,就不会造成界面响应迟钝。
创新互联公司成立于2013年,我们提供高端重庆网站建设、成都网站制作公司、成都网站设计公司、网站定制、成都营销网站建设、小程序制作、微信公众号开发、seo优化服务,提供专业营销思路、内容策划、视觉设计、程序开发来完成项目落地,为成都围栏护栏企业提供源源不断的流量和订单咨询。
在实现异步调用机制时,除了线程之外,还要用到回调。回调是一种双向调用,也就是,被调方在被调用时也会调用主调方的代码。在异步调用中,被调方需要在工作完成时通知主调方,即调用主调方的接口,这一机制通过回调实现。回调和异步调用的关系非常紧密,回调是异步调用的基础[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平台 实现异步调用的角色有如下三个角色:调用者 提货单 真实数据一个调用者在调用耗时操作 不能立即返回数据时 先返回一个提货单 然后在过一断时间后凭提货单来获取真正的数据 去蛋糕店买蛋糕 不需要等蛋糕做出来(假设现做要很长时间) 只需要领个提货单就可以了(去干别的事情) 等到蛋糕做好了 再拿提货单取蛋糕就可以了 public class Main { public static void main(String[] args) {
System out println( main BEGIN );
Host host = new Host();
Data data = host request( A );
Data data = host request( B );
Data data = host request( C );
System out println( main otherJob BEGIN );
try {
Thread sleep( );
} catch (InterruptedException e) {
}
System out println( main otherJob END );
System out println( data = + data getContent());
System out println( data = + data getContent());
System out println( data = + data getContent());
System out println( main END );
}
}
这里的main类就相当于 顾客 host就相当于 蛋糕店 顾客向 蛋糕店 定蛋糕就相当于 发请求request 返回的数据data是FutureData的实例 就相当于提货单 而不是真正的 蛋糕 在过一段时间后(sleep一段时间后) 调用data getContent() 也就是拿提货单获取执行结果
下面来看一下 顾客定蛋糕后 蛋糕店做了什么
public class Host {
public Data request(final int count final char c) {
System out println( request( + count + + c + ) BEGIN );
// ( ) 建立FutureData的实体
final FutureData future = new FutureData();
// ( ) 为了建立RealData的实体 启动新的线程
new Thread() {
public void run() {
//在匿名内部类中使用count future c
RealData realdata = new RealData(count c);
future setRealData(realdata);
}
} start();
System out println( request( + count + + c + ) END );
// ( ) 取回FutureData实体 作为传回值
return future;
}
}
host( 蛋糕店 )在接到请求后 先生成了 提货单 FutureData的实例future 然后命令 蛋糕师傅 RealData去做蛋糕 realdata相当于起个线程去做蛋糕了 然后host返回给顾客的仅仅是 提货单 future 而不是蛋糕 当蛋糕做好后 蛋糕师傅才能给对应的 提货单 蛋糕 也就是future setRealData(realdata)
下面来看看蛋糕师傅是怎么做蛋糕的
建立一个字符串 包含count个c字符 为了表现出犯法需要花费一些时间 使用了sleep
public class RealData implements Data { private final String content;
public RealData(int count char c) {
System out println( making RealData( + count + + c + ) BEGIN );
char[] buffer = new char[count];
for (int i = ; i count; i++) {
buffer[i] = c;
try {
Thread sleep( );
} catch (InterruptedException e) {
}
}
System out println( making RealData( + count + + c + ) END );
ntent = new String(buffer);
}
public String getContent() {
return content;
}
}
现在来看看 提货单 future是怎么与蛋糕 content 对应的:
public class FutureData implements Data { private RealData realdata = null;
private boolean ready = false;
public synchronized void setRealData(RealData realdata) {
if (ready) {
return; // 防止setRealData被调用两次以上
}
this realdata = realdata;
this ready = true;
notifyAll();
}
public synchronized String getContent() {
while (!ready) {
try {
wait();
} catch (InterruptedException e) {
}
}
return realdata getContent();
}
}
顾客做完自己的事情后 会拿着自己的 提货单 来取蛋糕
System out println( data = + data getContent());
这时候如果蛋糕没做好 就只好等了
while (!ready) { try {
wait();
} catch (InterruptedException e) {
}
//等做好后才能取到
return realdata getContent();
程序分析
对于每个请求 host都会生成一个线程 这个线程负责生成顾客需要的 蛋糕 在等待一段时间以后 如果蛋糕还没有做好 顾客还必须等待 直到 蛋糕被做好 也就是
future setRealData(realdata); 执行以后 顾客才能拿走蛋糕
每个线程只是专门负责制作特定顾客所需要的 蛋糕 也就是顾客A对应着蛋糕师傅A 顾客B对应着蛋糕师傅B 即使顾客B的蛋糕被先做好了 顾客A也只能等待蛋糕师傅A把蛋糕做好 换句话说 顾客之间没有竞争关系
lishixinzhi/Article/program/Java/gj/201311/27425
这个是最简单易懂的例子,自己手写的,还有一种是实现接口的,和这个差不多,需要的话可以给你写,希望对你有帮助
public class Test extends Thread{
private String name;
public Test() {
};
public Test (String name){
this.name=name;
}
public void run(){
for (int i = 0; i 1000; i++) {
System.out.println(name+"执行"+i);
}
}
public static void main(String[] args){
Test t1=new Test("AAA");
Test t2=new Test("BBB");
Test t3=new Test("CCC");
t1.start();
t2.start();
t3.start();
}
}
在整个思路上要调整一下
1、会有很多线程给一个队列上添加任务
2、有一个或者多个线程逐个执行队列的任务
考虑一下几点:
1、没有任务时,队列执行线程处于等待状态
2、添加任务时,激活队列执行线程,全部run起来,首先抢到任务的执行,其他全部wait
给个小例子吧
package org;
import java.util.LinkedList;
import java.util.List;
public class Queues {
public static ListTask queue = new LinkedListTask();
/**
* 假如 参数o 为任务
* @param o
*/
public static void add (Task t){
synchronized (Queues.queue) {
Queues.queue.add(t); //添加任务
Queues.queue.notifyAll();//激活该队列对应的执行线程,全部Run起来
}
}
static class Task{
public void test(){
System.out.println("我被执行了");
}
}
}
package org;
import java.util.List;
public class Exec implements Runnable{
@Override
public void run() {
while(true){
synchronized (Queues.queue) {
while(Queues.queue.isEmpty()){ //
try {
Queues.queue.wait(); //队列为空时,使线程处于等待状态
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("wait...");
}
Queues.Task t= Queues.queue.remove(0); //得到第一个
t.test(); //执行该任务
System.out.println("end");
}
}
}
public static void main(String[] args) {
Exec e = new Exec();
for (int i = 0; i 2; i++) {
new Thread(e).start(); //开始执行时,队列为空,处于等待状态
}
//上面开启两个线程执行队列中的任务,那就是先到先得了
//添加一个任务测试
Queues.Task t =new Queues.Task();
Queues.add(t); //执行该方法,激活所有对应队列,那两个线程就会开始执行啦
}
}
上面的就是很简单的例子了
我们在微信上24小时期待你的声音
解答本文疑问/技术咨询/运营咨询/技术建议/互联网交流