参考博客:https://blog.csdn.net/linchunquan/article/details/22382487

FutureTask可用于异步获取执行结果或取消执行任务的场景。通过传入Runnable或者Callable的任务给FutureTask,直接调用其run方法或者放入线程池执行,之后可以在外部通过FutureTask的get方法异步获取执行结果,因此,FutureTask非常适合用于耗时的计算,主线程可以在完成自己的任务后,再去获取结果。另外,FutureTask还可以确保即使调用了多次run方法,它都只会执行一次Runnable或者Callable任务,或者通过cancel取消FutureTask的执行等。

1. FutureTask执行多任务计算的使用场景

利用FutureTask和ExecutorService,可以用多线程的方式提交计算任务,主线程继续执行其他任务,当主线程需要子线程的计算结果时,在异步获取子线程的执行结果。

package com.cy.test.future;

import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.*; public class FutureTaskForMultiCompute { public static void main(String[] args) {
FutureTaskForMultiCompute inst = new FutureTaskForMultiCompute();
//创建任务集合
List<FutureTask<Integer>> taskList = new ArrayList<>();
ExecutorService exec = Executors.newFixedThreadPool(5);
for (int i = 0; i < 10; i++) {
// 传入Callable对象创建FutureTask对象
FutureTask<Integer> ft = new FutureTask<>(inst.new ComputeTask(i, "" + i));
taskList.add(ft);
// 提交给线程池执行任务,也可以通过exec.invokeAll(taskList)一次性提交所有任务;
exec.submit(ft);
} System.out.println("所有计算任务提交完毕, 主线程接着干其他事情!"); // 开始统计各计算线程计算结果
Integer totalResult = 0;
for (FutureTask<Integer> ft : taskList) {
try {
//FutureTask的get方法会自动阻塞,直到获取计算结果为止
totalResult = totalResult + ft.get();
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
}
} // 关闭线程池
exec.shutdown();
System.out.println("多任务计算后的总结果是:" + totalResult);
} private class ComputeTask implements Callable<Integer> {
private Integer result = 0;
private String taskName = ""; public ComputeTask(Integer iniResult, String taskName) {
this.result = iniResult;
this.taskName = taskName;
System.out.println("生成子线程计算任务: " + taskName);
} @Override
public Integer call() throws Exception {
for (int i = 0; i < 100; i++) {
result = +i;
} // 休眠5秒钟,观察主线程行为,预期的结果是主线程会继续执行,到要取得FutureTask的结果时等待直至完成。
Thread.sleep(5000);
System.out.println("子线程计算任务: " + taskName + " 执行完成!" + "result:" + result);
return result;
}
}
}

console:

生成子线程计算任务: 0
生成子线程计算任务: 1
生成子线程计算任务: 2
生成子线程计算任务: 3
生成子线程计算任务: 4
生成子线程计算任务: 5
生成子线程计算任务: 6
生成子线程计算任务: 7
生成子线程计算任务: 8
生成子线程计算任务: 9
所有计算任务提交完毕, 主线程接着干其他事情!
子线程计算任务: 0 执行完成!result:99
子线程计算任务: 3 执行完成!result:99
子线程计算任务: 4 执行完成!result:99
子线程计算任务: 2 执行完成!result:99
子线程计算任务: 1 执行完成!result:99
子线程计算任务: 9 执行完成!result:99
子线程计算任务: 8 执行完成!result:99
子线程计算任务: 7 执行完成!result:99
子线程计算任务: 6 执行完成!result:99
子线程计算任务: 5 执行完成!result:99
多任务计算后的总结果是:990

2. FutureTask在高并发环境下确保任务只执行一次

在很多高并发的环境下,往往我们只需要某些任务只执行一次。这种使用情景FutureTask的特性恰能胜任。举一个例子,假设有一个带key的连接池,当key存在时,即直接返回key对应的对象;当key不存在时,则创建连接。对于这样的应用场景,通常采用的方法为使用一个Map对象来存储key和连接池对应的对应关系,典型的代码如下面所示:

package com.cy.test.future;

import java.sql.Connection;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.locks.ReentrantLock; public class ConnectionPool { private Map<String, Connection> connectionPool = new HashMap<String, Connection>();
private ReentrantLock lock = new ReentrantLock(); public Connection getConnection(String key) {
try {
lock.lock();
if (connectionPool.containsKey(key)) {
return connectionPool.get(key);
} else {
Connection conn = createConnection();
connectionPool.put(key, conn);
return conn;
}
} finally {
lock.unlock();
}
} private Connection createConnection() {
return null;
}
}

在上面的例子中,我们通过加锁确保高并发环境下的线程安全,也确保了connection只创建一次,然而确牺牲了性能。改用ConcurrentHash的情况下,几乎可以避免加锁的操作,性能大大提高,但是在高并发的情况下有可能出现Connection被创建多次的现象。这时最需要解决的问题就是当key不存在时,创建Connection的动作能放在connectionPool之后执行,这正是FutureTask发挥作用的时机,基于ConcurrentHashMap和FutureTask的改造代码如下:

package com.cy.test.future;

import java.sql.Connection;
import java.util.concurrent.Callable;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.FutureTask; public class ConnectionPool2 {
private ConcurrentHashMap<String, FutureTask<Connection>> connectionPool = new ConcurrentHashMap<>(); public Connection getConnection(String key) throws Exception {
FutureTask<Connection> connectionTask = connectionPool.get(key);
if (connectionTask != null) {
return connectionTask.get();
} else {
FutureTask<Connection> newTask = new FutureTask<>(new Callable<Connection>() {
@Override
public Connection call() throws Exception {
return createConnection();
}
}); connectionTask = connectionPool.putIfAbsent(key, newTask);
if (connectionTask == null) {
connectionTask = newTask;
connectionTask.run();
}
return connectionTask.get();
}
} private Connection createConnection() {
return null;
}
}

经过这样的改造,可以避免由于并发带来的多次创建连接及锁的出现。

ConcurrentHashMap的putIfAbsent:

putIfAbsent方法主要是在向ConcurrentHashMap中添加键—值对的时候,它会先判断该键值对是否已经存在。

(1)如果是新的记录,那么会向map中添加该键值对,并返回null。 
(2)如果已经存在,那么不会覆盖已有的值,直接返回已经存在的值。

FutureTask的用法以及两种常用的使用场景的更多相关文章

  1. FutureTask的用法及两种常用的使用场景 + FutureTask的方法执行示意图

    from:  https://blog.csdn.net/linchunquan/article/details/22382487 FutureTask可用于异步获取执行结果或取消执行任务的场景.通过 ...

  2. FutureTask的用法及两种常用的使用场景

    FutureTask可用于异步获取执行结果或取消执行任务的场景.通过传入Runnable或者Callable的任务给FutureTask,直接调用其run方法或者放入线程池执行,之后可以在外部通过Fu ...

  3. js中replace的用法(两种常用举例,还有好多用法不一一列举)

    1.替换特定字符 <html><body> <script type="text/javascript"> var str="Visi ...

  4. Windows校验文件哈希hash的两种常用方式

    大家经常都到哪儿去下载软件和应用程序呢?有没想过下载回来的软件.应用程序或资源是否安全呢?在 Windows 10 和 Office 2016 发布当初,很多没权限的朋友都使用第三方网站去下载安装映像 ...

  5. Spring Cloud Config采用Git存储时两种常用的配置策略

    由于Spring Cloud Config默认采用了Git存储,相信很多团队在使用Spring Cloud的配置中心时也会采用这样的策略.即便大家都使用了Git存储,可能还有各种不同的配置方式,本文就 ...

  6. 两种常用的jquery事件加载的方法 的区别

    两种常用的jquery事件加载的方法   $(function(){});  window.onload=function(){}  第一个呢,是在DOM结构渲染完成以后调用的,这时候网页中一些资源还 ...

  7. iOS- 网络访问两种常用方式【GET & POST】实现的几个主要步骤

    1.前言 上次,在博客里谈谈了[GET & POST]的区别,这次准备主要是分享一下自己对[GET & POST]的理解和实现的主要步骤. 在这就不多废话了,直接进主题,有什么不足的欢 ...

  8. iOS- 网络请求的两种常用方式【GET & POST】的区别

    GET和POST 网络请求的两种常用方式的实现[GET & POST] –GET的语义是获取指定URL上的资源 –将数据按照variable=value的形式,添加到action所指向的URL ...

  9. 两种常用的全排列算法(java)

    问题:给出一个字符串,输出所有可能的排列. 全排列有多种算法,此处仅介绍常用的两种:字典序法和递归法. 1.字典序法: 如何计算字符串的下一个排列了?来考虑"926520"这个字符 ...

随机推荐

  1. python学习第五十一天os模块的用法

    os模块故名思意就是操作系统模块,该模块提供很多容许你的程序与操作系统直接交互的功能. 1,得到当前工作目录  os.getcwd() 2, 返回指定目录下的所有文件和目录名 os.listdir() ...

  2. 剑指offer 打印从1到最大的n位数

    题目描述: 输入数字n,按顺序打印出从1到最大的n位十进制数.比如输入3,则打印出1.2.3一直到最大的3位数999. 分析:注意不能直接输入最大的n位十进制数,因为可能属于大数,这个数无法用int或 ...

  3. windows 桌面背景设置实例

    应用SystemParametersInfo函数可以获取和设置数量众多的windows系统参数.这个小程序就是运用了SystemParametersInfo函数来设置桌面的墙纸背景,而且程序可以让我们 ...

  4. spring整合apache-shiro的简单使用

    这里不对shiro进行介绍,介绍什么的没有意义 初学初用,不求甚解,简单使用 一.导入jar包(引入坐标) <!--shiro和spring整合--> <dependency> ...

  5. Maven 添加其他Maven组件配置问题

    在父工程的pom文件里,添加如下配置 <project> <!--其它配置--> <modules> <module>A项目文件夹</module ...

  6. ztree点击加号+触发ajax请求

    之前做的时候一直是点击节点才触发ajax事件,配置如下:发现点击节点前面的“+”没有反应,后来发现,应该添加一个折叠的事件. onExpand:zTreeOnClick事件和onClick的一样. v ...

  7. “HTTP 错误 404.15 - Not Found 请求筛选模块被配置为拒绝包含的查询字符串过长的请求”之解决办法

    今天同事在做通过接口访问数据时,由于提交的一个参数内容比较多,导致测试时报了以下错误. 同时页面又给出了以下提示: 所以最终根据在网上找了相关资料总结出一下解决办法. 1. 在Web.config配置 ...

  8. Kettle整理

    下载kettle版本 (1)hadoop version 查看hadoop的版本    hadoop2.6 (2)则在data-integration\plugins\pentaho-big-data ...

  9. oracle删除数据文件

    在我们详细介绍之前,我们必须说清楚一点:Oracle不提供如删除表.视图一样删除数据文件的方法,数据文件是表空间的一部分,所以不能“移走”表空间. 一.使用offline数据文件的方法 非归档模式使用 ...

  10. 2019年8月12日~8月18日 第七周JAVA学习总结

    本周主要完成了PTA代码编写的任务,将报告也完成了,但是由于同学聚会比较多所以JAVA的学习比较少,也没有学习统一建模语言.用在编程的时间平均每天5个小时,用在JAVA学习上平均每天1个小时,用在解决 ...