从Java future 到 Guava ListenableFuture实现异步调用

置顶 2016年04月24日 09:11:14 皮斯特劳沃 阅读数:17570 标签: java异步调用线程非阻塞更多

 
版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/pistolove/article/details/51232004

本文是在学习中的总结,欢迎转载但请注明出处: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(); // 任务1
Future<Boolean> booleanTask = service.submit(new Callable<Boolean>() {
@Override
public 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;
}
} // 任务2
Future<String> stringTask = service.submit(new Callable<String>() {
@Override
public String call() throws Exception {
return "Hello World";
}
}); while (true) {
if (stringTask.isDone() && !stringTask.isCancelled()) {
String result = stringTask.get();
System.err.println("StringTask: " + result);
break;
}
} // 任务3
Future<Integer> integerTask = service.submit(new Callable<Integer>() {
@Override
public 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();
// 任务1
ListenableFuture<Boolean> booleanTask = service.submit(new Callable<Boolean>() {
@Override
public Boolean call() throws Exception {
return true;
}
}); Futures.addCallback(booleanTask, new FutureCallback<Boolean>() {
@Override
public void onSuccess(Boolean result) {
System.err.println("BooleanTask: " + result);
} @Override
public void onFailure(Throwable t) {
}
}); // 任务2
ListenableFuture<String> stringTask = service.submit(new Callable<String>() {
@Override
public String call() throws Exception {
return "Hello World";
}
}); Futures.addCallback(stringTask, new FutureCallback<String>() {
@Override
public void onSuccess(String result) {
System.err.println("StringTask: " + result);
} @Override
public void onFailure(Throwable t) {
}
}); // 任务3
ListenableFuture<Integer> integerTask = service.submit(new Callable<Integer>() {
@Override
public Integer call() throws Exception {
return new Random().nextInt(100);
}
}); Futures.addCallback(integerTask, new FutureCallback<Integer>() {
@Override
public void onSuccess(Integer result) {
try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.err.println("IntegerTask: " + result);
} @Override
public 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实现异步调用

    原文地址: http://blog.csdn.net/pistolove/article/details/51232004 Java Future     通过Executors可以创建不同类似的线程 ...

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

    java future模式 所线程实现异步调用(转载) 在多线程交互的中2,经常有一个线程需要得到另个一线程的计算结果,我们常用的是Future异步模式来加以解决.Future顾名思意,有点像期货市场 ...

  3. Future 异步回调 大起底之 Java Future 与 Guava Future

    目录 写在前面 1. Future模式异步回调大起底 1.1. 从泡茶的案例说起 1.2. 何为异步回调 1.2.1. 同步.异步.阻塞.非阻塞 1.2.2. 阻塞模式的泡茶案例图解 1.2.3. 回 ...

  4. SpringBoot系列——@Async优雅的异步调用

    前言 众所周知,java的代码是同步顺序执行,当我们需要执行异步操作时我们需要创建一个新线程去执行,以往我们是这样操作的: /** * 任务类 */ class Task implements Run ...

  5. java Future && Guava Future

    ### java future Runnable的任务是没有返回值,也不能抛出异常的java.util.concurrent.Callable接口,可以返回一个对象或者抛出异常 使用jdk的这种方式提 ...

  6. 006-优化web请求二-应用缓存、异步调用【Future、ListenableFuture、CompletableFuture】、ETag、WebSocket【SockJS、Stomp】

    四.应用缓存 使用spring应用缓存.使用方式:使用@EnableCache注解激活Spring的缓存功能,需要创建一个CacheManager来处理缓存.如使用一个内存缓存示例 package c ...

  7. Transform java future into completable future 【将 future 转成 completable future】

    Future is introduced in JDK 1.5 by Doug Lea to represent "the result of an asynchronous computa ...

  8. 浅谈Java Future接口

    Java项目编程中,为了充分利用计算机CPU资源,一般开启多个线程来执行异步任务.但不管是继承Thread类还是实现Runnable接口,都无法获取任务执行的结果.JDK 5中引入了Callable和 ...

  9. java Future 模式

    考慮這樣一個情況,使用者可能快速翻頁瀏覽文件中,而圖片檔案很大,如此在瀏覽到有圖片的頁數時,就會導致圖片的載入,因而造成使用者瀏覽文件時會有停頓 的現象,所以我們希望在文件開啟之後,仍有一個背景作業持 ...

随机推荐

  1. HDU 2512 一卡通大冒险 (第二类斯特林数)

    题目链接:HDU 2512 Problem Description 因为长期钻研算法, 无暇顾及个人问题,BUAA ACM/ICPC 训练小组的帅哥们大部分都是单身.某天,他们在机房商量一个绝妙的计划 ...

  2. Mac008--快捷键

    Mac008--快捷键 来自于百度经验:https://jingyan.baidu.com/article/08b6a591aac09614a909224f.html 快捷键图示 基本的快捷键 Com ...

  3. Vue CLI UI:Vue开发者必不可少的工具

    突然发现一个Vue cli 比较好用的工具,一个可视化图形界面方便你去创建.更新和管理Vue项目.这里分享2个作者写得比较好的文章 https://codeseeding.com/portal.php ...

  4. StringBuilder -字符串缓冲区,节约内层空间变长数组

    package cn.learn; /* 字符串缓冲区 -缓冲可提高效率 java.lang.StringBuilder 字符串的底层是一个被final修饰的数组,不能改变,是一个常量 private ...

  5. 0x3f3f3f3f 0xbfbfbfbf 等的原理及应用

    原理 0x的意思其实是十六进制,后面加的数其实就是一个十六进制数. 在十六进制中,我们知道a代表10,b代表11,c代表12,d代表13,e代表14,f代表15. 所以3f3f3f3f这个数用十进制数 ...

  6. 浅谈XML涉及到的常见技术(编写+解析)

    xml:即可扩展标记语言,用于描述关系型数据,也经常用作软件的配置文件:     1,编写xml文档一般基于一个约束文档,该文档用于规定xml的书写规范,常用的约束技术有        (1)XML ...

  7. echarts改变图例位置

    只需要legend属性中修改如下几个示数即可: legend:{ orient:"horizontal", x:'right', y:' center', width:'100', ...

  8. elasticsearch 基础 —— 分布式文档存储原理

    路由一个文档到一个分片中 当索引一个文档的时候,文档会被存储到一个主分片中. Elasticsearch 如何知道一个文档应该存放到哪个分片中呢?当我们创建文档时,它如何决定这个文档应当被存储在分片  ...

  9. shell条件判断命令test

  10. 一、WebFrom 图片上传

    一.代码实现了简单的图片上传功能(改一下也可以上传其他的),没有做图片大小和格式的判断,主要是熟悉fileupload控件 前台代码: <%@ Page Language="C#&qu ...