FutureTask的用法以及两种常用的使用场景
参考博客: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的用法以及两种常用的使用场景的更多相关文章
- FutureTask的用法及两种常用的使用场景 + FutureTask的方法执行示意图
from: https://blog.csdn.net/linchunquan/article/details/22382487 FutureTask可用于异步获取执行结果或取消执行任务的场景.通过 ...
- FutureTask的用法及两种常用的使用场景
FutureTask可用于异步获取执行结果或取消执行任务的场景.通过传入Runnable或者Callable的任务给FutureTask,直接调用其run方法或者放入线程池执行,之后可以在外部通过Fu ...
- js中replace的用法(两种常用举例,还有好多用法不一一列举)
1.替换特定字符 <html><body> <script type="text/javascript"> var str="Visi ...
- Windows校验文件哈希hash的两种常用方式
大家经常都到哪儿去下载软件和应用程序呢?有没想过下载回来的软件.应用程序或资源是否安全呢?在 Windows 10 和 Office 2016 发布当初,很多没权限的朋友都使用第三方网站去下载安装映像 ...
- Spring Cloud Config采用Git存储时两种常用的配置策略
由于Spring Cloud Config默认采用了Git存储,相信很多团队在使用Spring Cloud的配置中心时也会采用这样的策略.即便大家都使用了Git存储,可能还有各种不同的配置方式,本文就 ...
- 两种常用的jquery事件加载的方法 的区别
两种常用的jquery事件加载的方法 $(function(){}); window.onload=function(){} 第一个呢,是在DOM结构渲染完成以后调用的,这时候网页中一些资源还 ...
- iOS- 网络访问两种常用方式【GET & POST】实现的几个主要步骤
1.前言 上次,在博客里谈谈了[GET & POST]的区别,这次准备主要是分享一下自己对[GET & POST]的理解和实现的主要步骤. 在这就不多废话了,直接进主题,有什么不足的欢 ...
- iOS- 网络请求的两种常用方式【GET & POST】的区别
GET和POST 网络请求的两种常用方式的实现[GET & POST] –GET的语义是获取指定URL上的资源 –将数据按照variable=value的形式,添加到action所指向的URL ...
- 两种常用的全排列算法(java)
问题:给出一个字符串,输出所有可能的排列. 全排列有多种算法,此处仅介绍常用的两种:字典序法和递归法. 1.字典序法: 如何计算字符串的下一个排列了?来考虑"926520"这个字符 ...
随机推荐
- tabcontrol动态生成选项卡,并且在选项卡中添加窗体
http://blog.csdn.net/zx13525079024/article/details/6084733 今天在论坛上看到有人问到,如果在点击TRVEVIEW时动态生成tabcontrol ...
- ExtensionlessUrlHandler-Integrated-4.0
window 10 锁定webconfig解决方案 解决办法: 出现这个错误是因为 IIS 7 采用了更安全的 web.config 管理机制,默认情况下会锁住配置项不允许更改.要取消锁定可以以管理 ...
- js工厂函数创建对象与对象构造函数的理解
工厂函数,顾名思义,就是通过一个"工厂的加工" 来创建一个对象的函数 //工厂函数 function createPerson(name,sex){ sex = sex == '男' ? '女' : ...
- vscode 黑屏及类名报错解决方案
1.安装vscode之后打开黑屏,解决方案如下图,右键--属性--兼容性--勾选上 2.vscode 类名总报错 是ES2017的语法修饰器引起vscode警告. 解除的方法如果你使用的typescr ...
- CPU指令重排序与MESI缓存一致性
一.重排序场景 class ResortDemo { int a = 0; boolean flag = false; public void writer() { a = 1; //1 flag = ...
- zookeeper之一 安装和配置(单机+集群)
这里我以zookeeper3.4.10.tar.gz来演示安装,安装到/usr/local/soft目录下. 一.单机版配置 1.安装和配置 #.下载 wget http://apache.fayea ...
- vue token 过期处理
1.登陆成功后储存token 可以利用 vuex 储存token 2.利用路由守卫处理 router.beforeEach((to, from, next) => { }) 3.我 ...
- STM32CubeMX FreeRTOS no definition for "osThreadGetState" 解决办法
用STM32CubuMX默认加入的FreeRTOS默认配置eTaskGetState是禁止的 把该功能设为Enabled编译就不会出错了 IAR的编译器要勾选Allow VLA
- 点击链接跳转到QQ的情况; qq交谈
https://blog.csdn.net/qq_31856835/article/details/70225968 https://blog.csdn.net/qq_37815596/article ...
- 前端之CSS:CSS选择器
前端之css样式(选择器)... 一.css概述 CSS是Cascading Style Sheets的简称,中文称为层叠样式表,对html标签的渲染和布局 CSS 规则由两个主要的部分构成:选择器, ...