FutureTask是什么?

线程池的实现核心之一是FutureTask。在提交任务时,用户实现的Callable实例task会被包装为FutureTask实例ftask;提交后任务异步执行,无需用户关心;当用户需要时,再调用FutureTask#get()获取结果——或异常。

基本使用

方法中可能会调用到多个服务/方法,且这些服务/方法之间是互相独立的,不存在先后关系。在高并发场景下,如果执行比较耗时,可以考虑多线程异步的方式调用。

我们先模拟两个耗时服务

一个150ms,一个200ms

public class UserApi {

    /** 查询用户基本信息,模拟耗时150ms */
public String queryUserInfo(long userId) {
String userInfo = "userInfo: " + userId; try {
TimeUnit.MILLISECONDS.sleep(150L);
} catch (InterruptedException e) {
e.printStackTrace();
}
return userInfo;
} /** 查询用户地址,模拟耗时200ms */
public String queryUserAddress(long userId) {
String userAddress = "userAddress: " + userId; try {
TimeUnit.MILLISECONDS.sleep(200L);
} catch (InterruptedException e) {
e.printStackTrace();
}
return userAddress;
}
}

不使用FutureTask

@Test
public void testNotUseFutureTask() {
UserApi userApi = new UserApi(); long userId = 12;
long startTime = System.currentTimeMillis(); // 获取用户基本信息
String userInfo = userApi.queryUserInfo(userId);
// 获取用户地址
String userAddress = userApi.queryUserAddress(userId); System.err.println("testNotUseFutureTask 耗时:" + (System.currentTimeMillis() - startTime));
}

执行几次,结果:

testNotUseFutureTask 耗时:358
testNotUseFutureTask 耗时:360

从结果中,可以看到,总耗时是大于queryUserInfoqueryUserAddress之和的。但这两个服务逻辑上并不存在先后关系,理论上最长耗时取决于最慢的那个,即queryUserAddress

使用FutureTask

下例使用了FutureTask,来异步调用queryUserInfoqueryUserAddress

@Test
public void testUseFutureTask() throws ExecutionException, InterruptedException {
UserApi userApi = new UserApi(); long userId = 12;
long startTime = System.currentTimeMillis(); Callable<String> userInfoCallable = new Callable<String>() {
@Override
public String call() throws Exception {
return userApi.queryUserInfo(userId);
}
};
Callable<String> userAddressCallable = new Callable<String>() {
@Override
public String call() throws Exception {
return userApi.queryUserAddress(userId);
}
};
FutureTask<String> userInfoFutureTask = new FutureTask<>(userInfoCallable);
FutureTask<String> userAddressFutureTask = new FutureTask<>(userAddressCallable); new Thread(userInfoFutureTask).start();
new Thread(userAddressFutureTask).start(); String userInfo = userInfoFutureTask.get();
String userAddress = userAddressFutureTask.get();
System.err.println("testUseFutureTask 耗时:" + (System.currentTimeMillis() - startTime));
}

执行几次,结果:

testUseFutureTask 耗时:239
testUseFutureTask 耗时:237

很明显,总耗时大大减少了,这就验证了前面所说,总耗时取决于queryUserAddress的耗时。

实现一个简单的FutureTask

从前面使用FutureTask的代码中可以看到,一个FutureTask需要包含以下几点:

1、范型
2、构造函数,传入Callable
3、实现Runnable
4、有返回值
  • MyFutureTask代码如下:
public class MyFutureTask<T> implements Runnable {

    private Callable<T> callable;
private T result;
private String state; public MyFutureTask(Callable<T> callable) {
this.callable = callable;
} @Override
public void run() {
state = "NEW";
try {
result = callable.call();
} catch (Exception e) {
e.printStackTrace();
}
state = "DONE";
synchronized (this) {
this.notify();
}
} /** 获取调用结果 */
public T get() throws InterruptedException {
if ("DOEN".equals(state)) {
return result;
}
synchronized (this) {
this.wait();
}
return result;
}
}
  • 使用:
@Test
public void testMyUseFutureTask() throws InterruptedException {
UserApi userApi = new UserApi(); long userId = 12;
long startTime = System.currentTimeMillis(); Callable<String> userInfoCallable = new Callable<String>() {
@Override
public String call() throws Exception {
return userApi.queryUserInfo(userId);
}
};
Callable<String> userAddressCallable = new Callable<String>() {
@Override
public String call() throws Exception {
return userApi.queryUserAddress(userId);
}
}; // 不同点
MyFutureTask<String> userInfoFutureTask = new MyFutureTask<>(userInfoCallable);
MyFutureTask<String> userAddressFutureTask = new MyFutureTask<>(userAddressCallable); new Thread(userInfoFutureTask).start();
new Thread(userAddressFutureTask).start(); String userInfo = userInfoFutureTask.get();
String userAddress = userAddressFutureTask.get();
System.err.println("testMyUseFutureTask 耗时:" + (System.currentTimeMillis() - startTime));
}
  • 输出结果:
testMyUseFutureTask 耗时:208
testMyUseFutureTask 耗时:211

从结果中看到,预期与使用FutureTask的一致。至于使用我们自定义的MyFutureTask执行耗时为何会比FutureTask长,我猜测是我们自己写的未做更多的检查和判断。我们自己写的只是用来学习FutureTask

总结

不使用异步的方式时,queryUserAddressqueryUserInfo执行之后才会执行,两者相加的时间算入总调用耗时。如果使用了异步线程调用,由于queryUserAddress耗时长,这样在queryUserAddress执行结束前,queryUserInfo就执行结束了,这样queryUserInfo调用耗时就不计了。

FutureTask简单实战的更多相关文章

  1. Nancy简单实战之NancyMusicStore(六):写在最后

    前言 由于公司搬家后,住的地方离上班的地方远了N倍,以前是走路十多分钟就可以到公司的,上班时间也从9:00提早到8:30 现在每天上班都是先坐公交,然后再坐地铁,在这段路上比较浪费时间而且每天都是要6 ...

  2. 移动端web开发初探之Vuejs的简单实战

    这段时间在做的东西,是北邮人论坛APP的注册页.这个注册页是内嵌的网页,因为打算安卓和IOS平台同时使用.因此实际上就是在做移动端的web开发了. 在这过程中遇到了不少有意思的东西. DEMO的git ...

  3. Element ui结合springboot的简单实战

    Eelment UI简单实战 前端开发 1 创建项目,导入element ui(略) 2 大致设计出想要的效果,如下 3 创建包 根据设计的大致模样在项目的components中创建对应的包,方便以后 ...

  4. Nancy简单实战之NancyMusicStore(一):准备工作和搭建项目

    开发环境 OS : Windows 10 10.0.14393 IDE : Visual Studio 2015 Community With Update 3 Database : PostgreS ...

  5. Nancy简单实战之NancyMusicStore(三):完善商品信息与管理

    前言 上一篇,我们做了不少准备,并且还把我们NancyFx音乐商城的首页打造好了.这一篇主要是完善我们在首页的商品浏览问题和添加对商品的管理. 下面开始正题: 商品详情 首先是查看单个商品的详情: 先 ...

  6. GUI简单实战——贪吃蛇

    将前面学到的GUI基础知识完成实战,完成一个简单的贪吃蛇项目 项目功能 用键盘上下左右实现贪吃蛇的自动移动 贪吃蛇吃到食物后,长度加一,分数加一 贪吃蛇吃到自己的身体,则游戏结束 按空格键实现游戏的暂 ...

  7. Nancy简单实战之NancyMusicStore(二):打造首页

    前言 继上一篇搭建好项目之后,我们在这一篇中将把我们NancyMusicStore的首页打造出来. 布局 开始首页之前,我们要先为我们的整个应用添加一个通用的布局页面,WebForm中母版页的概念. ...

  8. Nancy简单实战之NancyMusicStore(四):实现购物车

    前言 上一篇,我们完成了商品的详情和商品的管理,这一篇我们来完成最后的一个购物车功能. 购物车,不外乎这几个功能:添加商品到购物车,删除购物车中的商品,对购物车中的商品进行结算. MVC MusicS ...

  9. gradle教程 [原创](eclipse/ADT下 非插件 非Android Studio/AS)纯手打 第二篇:gradle简单实战

    一个bug 一个脚印的叫你们用gradle. 1介于网络上的很多资料都是老的 不适用与现在的新版本gradle 尤其是有些gradle方法改名了老的用不了 2介于网上都是粘贴复制并且零碎我很蛋疼啊,走 ...

随机推荐

  1. spring boot 2.0.3+spring cloud (Finchley)8、微服务监控Spring Boot Admin

    参考:Spring Boot Admin 2.0 上手 Spring Boot Admin 用于管理和监控一个或多个Spring Boot程序,在 Spring Boot Actuator 的基础上提 ...

  2. Spring REST 异常处理

    在上一篇中写到了Spring MVC的异常处理,SpringMVC捕获到异常之后会转到相应的错误页面,但是我们REST API ,一般只返回结果和状态码,比如发生异常,只向客户端返回一个500的状态码 ...

  3. macbook 安装任意来源

    sudo spctl --master-disable Comand+r Csrutil disable Reboot

  4. 基本控件文档-UIView属性---iOS-Apple苹果官方文档翻译

    本系列所有开发文档翻译链接地址:iOS7开发-Apple苹果iPhone开发Xcode官方文档翻译PDF下载地址 //转载请注明出处--本文永久链接:http://www.cnblogs.com/Ch ...

  5. 【leetcode 简单】第九题 移除元素

    给定一个数组 nums 和一个值 val,你需要原地移除所有数值等于 val 的元素,返回移除后数组的新长度. 不要使用额外的数组空间,你必须在原地修改输入数组并在使用 O(1) 额外空间的条件下完成 ...

  6. h5 canvas动画,不知道谁写的

    <!DOCTYPE html><html lang="en"><head> <meta charset="UTF-8" ...

  7. JS 检测客户端断网情况

    常用方法 1 navigator.onLine 2 window.addEventListener() 3 获取网络资源 4 ajax请求 1. navigator.onLine 只会在机器未连上路由 ...

  8. 用C#实现对MSSqlServer数据库的增删改查---Server层(WaterLevelSetServer.cs、DeviceSetServer.cs)

    在Server层定义WaterLevelSetServer和WaterLevelRecordServer两个子类,分别继承DeviceSetServer和DeviceRecordServer. usi ...

  9. xargs -i 和-I 的区别【转】

    xargs与find经常结合来进行文件操作,平时删日志的时候只是习惯的去删除,比如  # find . -type f -name "*.log" | xargs rm -rf * ...

  10. 有关mysql的innodb_flush_log_at_trx_commit参数【转】

    一.参数解释 0:log buffer将每秒一次地写入log file中,并且log file的flush(刷到磁盘)操作同时进行.该模式下在事务提交的时候,不会主动触发写入磁盘的操作. 1:每次事务 ...