本文是在学习中的总结,欢迎转载但请注明出处:http://blog.csdn.net/pistolove/article/details/51232004


前言

随着移动互联网的蓬勃发展,手机App层出不穷,其业务也随之变得错综复杂。针对于开发人员来说,可能之前的一个业务只需要调取一次第三方接口以获取数据,而如今随着需求的增加,该业务需调取多个不同的第三方接口。通常,我们处理方法是让代码同步顺序的去调取这些接口。显然,调取接口数量的增加必然会造成响应时间的增加,势必会对系统性能造成一定影响。

为了保证系统响应迅速,需要寻找一种方法能够使调取接口能够异步执行,而java正好提供了类似的方法,在java.util.concurrent中包含了Future相关的类,运用其中的一些类可以进行异步计算,以减少主线程的等待时间。比如启动一个main方法,main中又包含了若干个其它任务,在不使用java future的情况下,main方法中的任务会同步阻塞执行,一个执行完成后,才能去执行另一个;如果使用java future,则main方法中的任务会异步执行,main方法不用等待一个任务的执行完成,只需往下执行就行。一个任务的执行结果又该怎么获取呢?这里就需要用到Future接口中的isDone()方法来判断任务是否执行完,如果完成完成则可获取结果,如果没有完成则需要等待,可见虽然主线程中的多个任务是异步执行,但是无法确定任务什么时候执行完成,只能通过不断去监听以获取结果,所以这里是阻塞的。这样,可能某一个任务执行时间很长会拖累整个主任务的执行。

针对这样的情况,google对java.util.concurrent中的许多类进行封装,最终产生了google guava框架,其中com.google.common.util中的ListenableFuture就是本文要叙述的重点。查看com.google.common.util,发现其中的很多类都是对java.util.concurrent的封装,以增加特有的方法。ListenableFuture扩展了future方法,增加了addListener方法,该方法可以监听线程,并通过回调函数来获取结果,达到线程之间异步非阻塞执行。

首先,了解下同步、异步、阻塞、非阻塞相关概念;其次,简单介绍java future和guava future相关技术,并通过示例代码进一步对其进行理解;最后,对java future和guava future进行比较。


同步、异步、阻塞、非阻塞

同步:所谓同步,就是在发出一个功能调用时,在没有得到结果之前,该调用就不返回。也就是必须一件一件事做,等前一件做完了才能做下一件事。

异步:异步的概念和同步相对。当一个异步过程调用发出后,调用者不能立刻得到结果。实际处理这个调用的部件在完成后,通过状态、通知和回调来通知调用者。

阻塞:阻塞调用是指调用结果返回之前,当前线程会被挂起(线程进入非可执行状态,在这个状态下,cpu不会给线程分配时间片,即线程暂停运行)。函数只有在得到结果之后才会返回。

非阻塞:非阻塞和阻塞的概念相对应,指在不能立刻得到结果之前,该函数不会阻塞当前线程,而会立刻返回。


Java future

减少主函数的等待时间,使得原本需要等待的时间段可以处理其它事情

1、Executors创建线程池的几种常见方式

通过Executors可以创建不同类似的线程池,常见的大概有下表几种类型,还有些可能未被列出。在实际应用中,个人感觉主要使用newCachedThreadPool和newFixedThreadPool来创建线程池。

类名 说明
newCachedThreadPool 缓存型池子,先查看池中有没有以前建立的线程,如果有,就reuse;如果没有,就建一个新的线程加入池中。缓存型池子通常用于执行一些生存期很短的异步型任务。因此在一些面向连接的daemon型SERVER中用得不多。能reuse的线程,必须是timeout IDLE内的池中线程,缺省timeout为60s,超过这个IDLE时长,线程实例将被终止并移出池子。注意:放入CachedThreadPool的线程超过TIMEOUT不活动,其会自动被终止。
newFixedThreadPool 和cacheThreadPool类似,有可用的线程就使用,但不能随时建新的线程。其独特之处:任意时间点,最多只能有固定数目的活动线程存在,此时如果有新的线程要建立,只能放在另外的队列中等待,直到当前的线程中某个线程终止直接被移出池子。cache池和fixed池调用的是同一个底层池,只不过参数不同:fixed池线程数固定,并且是0秒IDLE(无IDLE)。所以FixedThreadPool多数针对一些很稳定很固定的正规并发线程,多用于服务器。cache池线程数支持0-Integer.MAX_VALUE(显然完全没考虑主机的资源承受能力),60秒IDLE。
ScheduledThreadPool 调度型线程池。这个池子里的线程可以按schedule依次delay执行,或周期执行。
SingleThreadExecutor 单例线程,任意时间池中只能有一个线程。用的是和cache池和fixed池相同的底层池,但线程数目是1-1,0秒IDLE(无IDLE)。

2、Executors创建线程池源码

//调用newCachedThreadPool方法,可以创建一个缓冲型线程池,而在改方法中通过传参创建一个ThreadPoolExecutor,我么你会很奇怪明明返回的是一个ExecutorService,怎么会创建了一个ThreadPoolExecutor呢?
public static ExecutorService newCachedThreadPool() {return new ThreadPoolExecutor(0, Integer.MAX_VALUE,60L, TimeUnit.SECONDS, new SynchronousQueue<Runnable());
}// ThreadPoolExecutor继承了抽象的service类AbstractExecutorService
public class ThreadPoolExecutor extends AbstractExecutorService {}//AbstractExecutorService实现了ExecutorService接口
public abstract class AbstractExecutorService implements ExecutorService {}//所以ExecutorService其实是ThreadPoolExecutor的基类,这也就解释清楚了

3、ExecutorService(线程池)

ExecutorService是一个接口,它继承了Executor,在原有execute方法的基础上新增了submit方法,传入一个任务,该方法能够返回一个Future对象,可以获取异步计算结果。

//ExecutorService继承了Executor,并扩展了新方法。
public interface ExecutorService extends Executor { }//Executor中的方法
void execute(Runnable command);//增加了submit方法,该方法传任务来获取Future对象,而Future对象中可以获取任务的执行结果
<T> Future<T> submit(Callable<T> task);
Future<?> submit(Runnable task);

4、Future(获取异步计算结果)

Future接口中有下表所示方法,可以获取当前正在执行的任务相关信息。

方法 说明
boolean cancel(boolean interruptIf) 取消任务的执行
boolean isCancelled() 任务是否已取消,任务正常完成前将其取消,返回 true
boolean isDone() 任务是否已完成,任务正常终止、异常或取消,返回true
V get() 等待任务结束,然后获取V类型的结果
V get(long timeout, TimeUnit unit) 获取结果,设置超时时间

5、FutureTask

Executor框架利用FutureTask来完成异步任务,并可以用来进行任何潜在的耗时的计算。一般FutureTask多用于耗时的计算,主线程可以在完成自己的任务后,再去获取结果。

FutureTask包装了Callable和Runnable接口对象,提供对Future接口的基本实现,开始、取消计算、查询计算是否完成、获取计算结果。仅当计算完成时才能检索结果,当计算没有完成时,该方法会一直阻塞直到任务转入完成状态。一旦完成计算,不能够重新开始或取消计算。通过Excutor(线程池)来执行,也可传递给Thread对象执行。如果在主线程中需要执行比较耗时的操作时,但又不想阻塞主线程时,可以把这些作业交给Future对象在后台完成,当主线程将来需要时,就可以通过Future对象获得后台作业的计算结果或者执行状态。

//通过传入任务来构造FutureTask
public FutureTask(Callable<V> callable) {}
public FutureTask(Runnable runnable, V result) {}//FutureTask中同样有获取当前任务状态的方法
public boolean isCancelled(){}
public boolean isDone() {}
public boolean cancel(boolean mayInterruptIfRunning) {}//FutureTask实现RunnableFuture
public class FutureTask<V> implements RunnableFuture<V> {}//RunnableFuture继承Runnable和Future
public interface RunnableFuture<V> extends Runnable, Future<V> 

6、示例代码

package future.java;import java.util.Random;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;public class TestFuture {// 创建线程池final static ExecutorService service = Executors.newCachedThreadPool();public static void main(String[] args) throws InterruptedException, ExecutionException {Long t1 = System.currentTimeMillis();// 任务1Future<Boolean> booleanTask = service.submit(new Callable<Boolean>() {@Overridepublic Boolean call() throws Exception {return true;}});while (true) {if (booleanTask.isDone() && !booleanTask.isCancelled()) {//模拟耗时Thread.sleep(500);Boolean result = booleanTask.get();System.err.println("BooleanTask: " + result);break;}}// 任务2Future<String> stringTask = service.submit(new Callable<String>() {@Overridepublic String call() throws Exception {return "Hello World";}});while (true) {if (stringTask.isDone() && !stringTask.isCancelled()) {String result = stringTask.get();System.err.println("StringTask: " + result);break;}}// 任务3Future<Integer> integerTask = service.submit(new Callable<Integer>() {@Overridepublic Integer call() throws Exception {return new Random().nextInt(100);}});while (true) {if (integerTask.isDone() && !integerTask.isCancelled()) {Integer result = integerTask.get();System.err.println("IntegerTask: " + result);break;}}// 执行时间System.err.println("time: " + (System.currentTimeMillis() - t1));}}

Guava future

减少主函数的等待时间,使得多任务能够异步非阻塞执行


ListenableFuture是可以监听的Future,它是对java原生Future的扩展增强。Future表示一个异步计算任务,当任务完成时可以得到计算结果。如果希望计算完成时马上就拿到结果展示给用户或者做另外的计算,就必须使用另一个线程不断的查询计算状态。这样做会使得代码复杂,且效率低下。如果使用ListenableFuture,Guava会帮助检测Future是否完成了,如果完成就自动调用回调函数,这样可以减少并发程序的复杂度。

1、MoreExecutors

该类是final类型的工具类,提供了很多静态方法。例如listeningDecorator方法初始化ListeningExecutorService方法,使用此实例submit方法即可初始化ListenableFuture对象。


2、ListeningExecutorService

该类是对ExecutorService的扩展,重写ExecutorService类中的submit方法,返回ListenableFuture对象。


3、ListenableFuture

该接口扩展了Future接口,增加了addListener方法,该方法在给定的excutor上注册一个监听器,当计算完成时会马上调用该监听器。不能够确保监听器执行的顺序,但可以在计算完成时确保马上被调用。


4、FutureCallback

该接口提供了OnSuccess和OnFailuren方法。获取异步计算的结果并回调。


5、Futures

该类提供和很多实用的静态方法以供使用。


6、ListenableFutureTask

该类扩展了FutureTask类并实现ListenableFuture接口,增加了addListener方法。


7、实例代码

package future.guava;import java.util.Random;
import java.util.concurrent.Callable;
import java.util.concurrent.Executors;
import com.google.common.util.concurrent.FutureCallback;
import com.google.common.util.concurrent.Futures;
import com.google.common.util.concurrent.ListenableFuture;
import com.google.common.util.concurrent.ListeningExecutorService;
import com.google.common.util.concurrent.MoreExecutors;public class TestListenableFuture2 {// 创建线程池final static ListeningExecutorService service = MoreExecutors.listeningDecorator(Executors.newCachedThreadPool());public static void main(String[] args) throws Exception {Long t1 = System.currentTimeMillis();// 任务1ListenableFuture<Boolean> booleanTask = service.submit(new Callable<Boolean>() {@Overridepublic Boolean call() throws Exception {return true;}});Futures.addCallback(booleanTask, new FutureCallback<Boolean>() {@Overridepublic void onSuccess(Boolean result) {System.err.println("BooleanTask: " + result);}@Overridepublic void onFailure(Throwable t) {}});// 任务2ListenableFuture<String> stringTask = service.submit(new Callable<String>() {@Overridepublic String call() throws Exception {return "Hello World";}});Futures.addCallback(stringTask, new FutureCallback<String>() {@Overridepublic void onSuccess(String result) {System.err.println("StringTask: " + result);}@Overridepublic void onFailure(Throwable t) {}});// 任务3ListenableFuture<Integer> integerTask = service.submit(new Callable<Integer>() {@Overridepublic Integer call() throws Exception {return new Random().nextInt(100);}});Futures.addCallback(integerTask, new FutureCallback<Integer>() {@Overridepublic void onSuccess(Integer result) {try {Thread.sleep(500);} catch (InterruptedException e) {e.printStackTrace();}System.err.println("IntegerTask: " + result);}@Overridepublic void onFailure(Throwable t) {}});// 执行时间System.err.println("time: " + (System.currentTimeMillis() - t1));}}

Java future 和 Guava future的对比

Future 具有局限性。在实际应用中,当需要下载大量图片或视频时,可以使用多线程去下载,提交任务下载后,可以从多个Future中获取下载结果,由于Future获取任务结果是阻塞的,所以将会依次调用Future.get()方法,这样的效率会很低。很可能第一个下载速度很慢,则会拖累整个下载速度。

Future主要功能在于获取任务执行结果和对异步任务的控制。但如果要获取批量任务的执行结果,从上面的例子我们已经可以看到,单使用 Future 是很不方便的。其主要原因在于:一方面是没有好的方法去判断第一个完成的任务;另一方面是 Future的get方法 是阻塞的,使用不当会造成线程的浪费。第一个问题可以用 CompletionService 解决,CompletionService 提供了一个 take() 阻塞方法,用以依次获取所有已完成的任务。第二个问题可以用 Google Guava 库所提供的 ListeningExecutorService 和 ListenableFuture 来解决。除了获取批量任务执行结果时不便,Future另外一个不能做的事便是防止任务的重复提交。要做到这件事就需要 Future 最常见的一个实现类 FutureTask 了。Future只实现了异步,而没有实现回调,主线程get时会阻塞,可以轮询以便获取异步调用是否完成。

在实际的使用中建议使用Guava ListenableFuture来实现异步非阻塞,目的就是多任务异步执行,通过回调的方方式来获取执行结果而不需轮询任务状态。

希望本文对你有所帮助。下一篇文章将讲解RestTemplate和AsyncRestTemplate相关技术。

从Java future 到 Guava ListenableFuture实现异步调用相关推荐

  1. 从Java Future 到 Guava ListenableFuture实现异步非阻塞调用

    前言 随着移动互联网的蓬勃发展,手机App层出不穷,其业务也随之变得错综复杂.针对于开发人员来说,可能之前的一个业务只需要调取一次第三方接口以获取数据,而如今随着需求的增加,该业务需调取多个不同的第三 ...

  2. java future模式 所线程实现异步调用

    2019独角兽企业重金招聘Python工程师标准>>> 在多线程交互的中,经常有一个线程需要得到另个一线程的计算结果,我们常用的是Future异步模式来加以解决. Future顾名思 ...

  3. Guava ListenableFuture实现异步非阻塞调用

    为了保证系统响应迅速,需要寻找一种方法能够使调取接口能够异步执行,而Java正好提供了类似的方法,在java.util.concurrent中包含了Future相关的类,运用其中的一些类可以进行异步计 ...

  4. phpcms 指定id范围 调用_Elasticsearch v7 中Java High-Level REST Client同步和异步调用

    每个Elasticsearch API 支持同步/异步两种方式,同步方法直接返回一个结果对象.异步的方法以async为后缀,通过listener参数来通知结果. 同步执行 以下列方式执行IndexRe ...

  5. scala 异步调用_非阻塞异步Java 8和Scala的Try / Success / Failure

    scala 异步调用 受Heinz Kabutz最近的时事通讯以及我在最近的书中研究的Scala的期货的启发,我着手使用Java 8编写了一个示例,该示例如何将工作提交给执行服务并异步地响应其结果,并 ...

  6. Java多线程实现异步调用

    在Java平台,实现异步调用的角色有如下三个角色:调用者. 提货单 .真实数据,一个调用者在调用耗时操作,不能立即返回数据时,先返回一个提货单 .然后在过一断时间后凭提货单来获取真正的数据.去蛋糕店买 ...

  7. java的rest异步调用_使用AsyncRestTemplate进行异步调用

    背景: 最近项目中需要并发调用c++服务的http接口,问题是同时调用两个接口时,会发生严重阻塞,导致页面响应慢,还经常遇到接收数据超时,导致RestTemplate报出ReadTimeout错误,一 ...

  8. java 异步调用方法_java异步调用方法有哪些?如何实现异步调用?

    你知道java异步调用方法都有哪些吗?下面的文章内容,就对这方面的问题做了一下整理,一起来看看java异步调用的方法吧! 1.利用Spring的异步方法去执行 注:没有返回值 在启动类又或者是配置类加 ...

  9. java http异步调用_HttpClient的异步调用,你造吗?

    一.前言 HttpClient提供了两种I/O模型:经典的java阻塞I/O模型和基于Java NIO的异步非阻塞事件驱动I/O模型. Java中的阻塞I/O是一种高效.便捷的I/O模型,非常适合并发 ...

最新文章

  1. 极客时间《玩转Git三剑客》之GItHub剑客
  2. 现在还有必要学Java开发吗?前景好吗?
  3. 创建一个QT for Android的传感器应用应用程序(摘自笔者2015年将出的《QT5权威指南》,本文为试读篇)
  4. java怎样调用DLL方法
  5. 这可能是把Docker的概念讲的最清楚的一篇文章
  6. HTTP系列学习(笔记二):HTTPS与HTTP的区别在哪?
  7. for循环连续创建对象
  8. java后根次序非递归输出_求根结点到每个叶子节点的逆序列【后序遍历非递归的应用】...
  9. HBase Shell命令大全
  10. 视频压缩 I P B 帧 详解
  11. BZOJ1050 [HAOI2006]旅行comf (并查集)
  12. Python世界里的魔术方法(一)
  13. 《计算机网络--自顶向下方法》第三章--运输层
  14. OSI七层网络模型和网络协议
  15. count()--不是单组分组函数
  16. 数据分析中,文本分析远比数值型分析重要!(上)
  17. 逻辑英语结构【重点】
  18. 从源码解析-Android中Zygote进程是如何fork一个APP进程的
  19. 特斯拉电动汽车接连发生两起致命车祸 美国监管机构已介入调查
  20. arcgis九段线、南海诸岛

热门文章

  1. lol转区服务器怎么一直维护,LOL转区系统将再度开启,“大量物品无法转移,玩家直言抓紧用”...
  2. Uva 12563,劲歌金曲,01背包
  3. 工作中有时候情商比工作技能更重要
  4. java实现装饰模式,JAVA实现装饰模式
  5. 巧用Vlookup、If、Or等函数解决数据合并问题
  6. 《史记.本拉登列传》
  7. Alan Kay——面向对象之父:预测未来,创造未来
  8. CTF-Web1-(涉及cbc字节翻转攻击,难度大)
  9. endnote插入中英文期刊参考文献混排以及模板下载
  10. 神武手游宠物打书模拟器