基于JAVA的Promise模式实现

CarPost 9年前

来自: http://my.oschina.net/andylucc/blog/608499


Promise模式是一种异步编程模式,即我们可以先开始一个任务,拿到这个任务的凭据而并不需要立即得到这个任务的执行结果才继续往下执行,我们拿着这个凭证可以在之后任何需要的时候去兑换结果。这篇文章主要介绍一种基于JAVA的Promise模式实现并结合一些例子。


原始实现

为了能够让大家对这个模式有个印象,我举个简单点的例子,假如我们正在做百度百科这个页面,我们需要给前端提供一个服务(下面的代码我们将结果打印出来来模拟),可以根据id获取百科条目的内容,具体的例子是我们想要获取某个明星的百度百科信息。而这个明星的信息有两个相关内容可能需要调用别人的服务来获取,一个是获取明星相关的人物信息;另一个是获取这个明星相关的新闻。我们假设这两个服务分别位于不同的业务部门,而且由于业务的复杂性,服务比较慢,我用下面的代码来表示:

/**   * @author float.lu   */  public class OldLongCallExample {      public static void main (String ...s) {          long start = System.nanoTime();          //280ms          String result1 = getRelatedRoles();          //250ms          String result2 = getRelatedNews();          System.out.println("Result:" + result1 + result2);          System.out.println("take:" + TimeUnit.NANOSECONDS.toMillis((System.nanoTime() - start)) + "ms");      }

上面的代码我们可以看出,我们首先调用getRelatedRoles获取明星相关人物,这里需要花费280ms,拿到明星相关人物之后我们继续调用getRelatedNew获取明星相关新闻,这里需要250ms,我们总共需要约530ms,这个时间是很长的。


思考

其实对于上面的这种情况,我们在调用getRelatedRoles之后其实并不需要等待getRelatedRoles给出结果之后在往下进行,因为后面花费时间较长的部分getRelatedNews并不依赖于getRelatedRoles的结果,因此我们顺利成章的认为我们可以在调用getRelatedRoles之后不需要等待,我们继续调用getRelatedNews,当主方法需要返回的时候我们再分别取拿getRelatedRoles和getRelatedNews的结果,这就是本文基于Promise模式的实现要解决的事情。


基于JPromise的实现

JPromise是基于JAVA的Promise的实现,在抽象类Promise中我们主要提供了三个抽象方法:

/**   * 获取执行结果   * @return   */  public abstract V get();    /**   * 带超时时间的获取执行结果   * @param timeout   * @param unit   * @return   */  public abstract V get(long timeout, TimeUnit unit);    /**   * 获取异常信息   * @return   */  public abstract Throwable getException();

和一个将普通代码逻辑包装成Promise的工具方法:

/**   * 将普通逻辑包装成Promise   * @param task   * @param <V>   * @return   */  public static <V> Promise<V> wrap(final AsyncTask<V> task){      if (executorService == null) {          setDefaultExecutorService();      }      return new DefaultPromise<V>(executorService.submit(new Callable<V>() {          @Override          public V call() throws Exception {              return task.call();          }      }));  }

我们通过JPromise可以将不同的业务逻辑包装成Promise对象,我们把Promise对象称作凭证,通过Promise抽象类提供的get或get(timeout,unit)方法来在需要的时候根据凭证获取结果,同时允许超时。那么最开始的实现可以改造成下面的样子:

/**   * @author float.lu   */  public class PromiseLongCallExample {      public static void main (String ...s) {          long start = System.nanoTime();          //提交任务不需要立即拿到结果          Promise<String> resolve1 = Promise.wrap(new AsyncTask<String>() {              @Override              public String call() throws Exception {                  return getRelatedRoles();              }          });          //提交任务不需要立即拿到结果          Promise<String> resolve2 = Promise.wrap(new AsyncTask<String>() {              @Override              public String call() throws Exception {                  return getRelatedNews();              }          });            System.out.println("Result:" + resolve1.get() + resolve2.get());          System.out.println("take:" + TimeUnit.NANOSECONDS.toMillis((System.nanoTime() - start)) + "ms");      }

经过测试,上面的代码执行时间约为280ms,相比最开始的530ms得到了很大的优化。


异常捕获

通常异步执行的逻辑都会面临着异常捕获的问题,Jpromise的实现将逻辑异常捕获并通过getException向外面提供访问接口。


自定义线程池

基于JPromise的异步任务都是由额外的线程执行的,因此我们需要为JPromise配置线程池。JPromise支持用户自定义线程池,包括基于Spring容器的配置,只需要将自定义的ExecutorService注入到PromiseFigure的executorService属性即可。


总结

Promise模式的适用场景也是有限的,当下层逻辑不依赖于上层逻辑,这些逻辑通常是串行执行的时候可以使用Promise模式来进行优化,优化效果还是很显著的。


项目地址:https://git.oschina.net/floatlu/jpromise