欢迎您访问 最编程 本站为您分享编程语言代码,编程技术文章!
您现在的位置是: 首页

异步回调的 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的结果。

推荐阅读