Java回调实现异步 (转)

出处: Java回调实现异步

 

  在正常的业务中使用同步线程,如果服务器每处理一个请求,就创建一个线程的话,会对服务器的资源造成浪费。因为这些线程可能会浪费时间在等待网络传输,等待数据库连接等其他事情上,真正处理业务逻辑的时间很短很短,但是其他线程在线程池满了之后又会阻塞,等待前面的线程处理完成。而且,会出现一个奇怪的现象,客户端的请求被阻塞,但是cpu的资源使用却很低,大部分线程都浪费在处理其他事情上了。所以,这就导致服务器并发量不高。

  而异步,则可以解决这个问题。

  我们可以把需要用到cpu的业务处理使用异步来实现,这样其他请求就不会被阻塞,而且cpu会保持比较高的使用率。

  今天就学习了使用回调来实现异步的方法。我们设想一个情景,A是处理业务的一个步骤,A需要解决一个问题,这时候A可以问B,让B来告诉A答案,这期间,A可以继续做自己的事情,而不用因为B做的事而阻塞。于是,我们想到给B设置一个线程,让B去处理耗时的操作,然后处理完之后把结果告诉A。所以这个问题的要点就在于B处理完之后如何把结果告诉A。我们可以直接在A中写一个方法对B处理完的结果进行处理,然后B处理完之后调用A这个方法。这样A调用B去处理过程,B调用A的C方法去处理结果就叫做回调。

package CallBack;

public interface CallBack {
    /*
    *A处理结果的方法,为什么要写这个接口呢?
    *因为可能不止A需要用到B的处理过程,如果很多地方需要用到B
    * 那么传入B的方法就不可能只传A类,所以要定义一个接口,
    * 传入B的处理方法的参数就是这个接口对象
    * */
    public void solve(String result);
}
package CallBack;

public class A implements CallBack {
    private B b;

    public A(B b){
        this.b=b;
    }

    //A需要解决一个问题,所以他把问题交给B处理,B单独创建一个线程,不影响A的运行
    public void ask(final String question){
        System.out.println("A问了B一个问题");
        new Thread(()->{
            //B想要帮A处理东西,就必须知道谁让自己处理的,所以要传入a,也要知道a想处理什么,所以要传入question
            b.executeMessage(A.this,question);
        }).start();
        //A把要处理的事情交给b之后,就可以自己去玩耍了,或者去处理其他事情
        play();
    }

    public void play(){
        System.out.println("我要逛街去了");
    }

    //A拿到了B处理完成的结果,可以进行一些操作,比如把结果输出
    @Override
    public void solve(String result) {
        System.out.println("B告诉A的答案是--》"+result);
    }

}
package CallBack;

public class B {
    public void executeMessage(CallBack callBack,String question){
        System.out.println(callBack.getClass()+"问的问题--》"+question);
        try {
            Thread.sleep(3000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        String result="答案是2";
        callBack.solve(result);
    }
}
package CallBack;

public class test {
    public static void main(String[] args) {
        B b=new B();
        A a=new A(b);
        a.ask("1+1=?");
    }
}

console结果:

运行结果:
A问了B一个问题
我要逛街去了
class CallBack.A问的问题--》1+1=?
B告诉A的答案是--》答案是2

Process finished with exit code 0

 

异步回调的实现依赖于多线程或者多进程

  软件模块之间总是存在着一定的接口,从调用方式上,可以把他们分为三类:同步调用回调异步调用。同步调用是一种阻塞式调用,调用方要等待对方执行完毕才返回,它是一种单向调用;回调是一种双向调用模式,也就是说,被调用方在接口被调用时也会调用对方的接口;异步调用是一种类似消息或事件的机制,不过它的调用方向刚好相反,接口的服务在收到某种讯息或发生某种事件时,会主动通知客户方(即调用客户方的接口)。回调和异步调用的关系非常紧密,通常我们使用回调来实现异步消息的注册,通过异步调用来实现消息的通知

 

多线程中的“回调” (JDK8之前)

  Java多线程中可以通过callable和future或futuretask结合来获取线程执行后的返回值。实现方法是通过get方法来调用callable的call方法获取返回值。

  其实这种方法本质上不是回调,回调要求的是任务完成以后被调用者主动回调调用者的接口。而这里是调用者主动使用get方法阻塞获取返回值。

一般情况下,我们会结合Callable和Future一起使用,通过ExecutorService的submit方法执行Callable,并返回Future。

public class 多线程中的回调 {
    //这里简单地使用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());
}
@Test
public void test () {
    Callable callable = new Callable() {
        @Override
        public Object call() throws Exception {
            return null;
        }
    };
    FutureTask futureTask = new FutureTask(callable);

}
}

  比起future.get(),其实更推荐使用get (long timeout, TimeUnit unit) 方法,设置了超时时间可以防止程序无限制的等待future的结果。

 

CompletableFuture介绍(JDK8)

Future模式的缺点

  • Future虽然可以实现获取异步执行结果的需求,但是它没有提供通知的机制,我们无法得知Future什么时候完成。

  • 要么使用阻塞,在future.get()的地方等待future返回的结果,这时又变成同步操作。要么使用isDone()轮询地判断Future是否完成,这样会耗费CPU的资源。

CompletableFuture

  Netty、Guava分别扩展了Java 的 Future 接口,方便异步编程。

  Java 8新增的CompletableFuture类正是吸收了所有Google Guava中ListenableFuture和SettableFuture的特征,还提供了其它强大的功能,让Java拥有了完整的非阻塞编程模型:Future、Promise 和 Callback(在Java8之前,只有无Callback 的Future)。

  CompletableFuture能够将回调放到与任务不同的线程中执行,也能将回调作为继续执行的同步函数,在与任务相同的线程中执行。它避免了传统回调最大的问题,那就是能够将控制流分离到不同的事件处理器中。

  CompletableFuture弥补了Future模式的缺点。在异步的任务完成后,需要用其结果继续操作时,无需等待。可以直接通过thenAccept、thenApply、thenCompose等方式将前面异步处理的结果交给另外一个异步事件处理线程来处理。

 

相关详细API内容介绍: 理解Java8里面CompletableFuture异步编程

 

posted @ 2019-11-13 17:30  myseries  阅读(2337)  评论(0编辑  收藏  举报