FutureTask是future的实现类,它同时实现了两个接口:Runnable和Future,所以它既可以作为Runnable被线程执行,又可以作为Future得到Callable的返回值。

因此我们可以:
- 调用FutureTask对象的run()方法执行
- 调用FutureTask对象的get()方法取结果
- 也可以将FutureTask对象作为callable的实现用线程池或者Thread类去执行。

FutureTask有两个很重要的属性分别是 state 、runner 。futureTask之所以可以支持cancel操作 就是因为这两个属性
其中 state为 枚举值:
NEW 新建 0
COMPLETING 执行中 1
NORMAL 正常 2
EXCEPTIONAL 异常 3
CANCELLED 取消 4
INTERRUPTING 中断中 5
INTERRUNPED 被中断 6

state的状态变化可以有四种方式
NEW->COMPLETING->NORMAL 正常完成的流程
NEW->COMPLETING->EXCEPTIONAL 出现异常的流程
NEW->CANCELED 被取消
NEW->INTERRUNPING->INTERRRUNPTED 被中断

我们用FutureTask改写一下前文的例子:
public class FutureDemo { public static void main(String[] args) { /*
定义生产者:用来做月饼的Callable */ final Callable<Integer> callable = new
Callable<Integer>() {public Integer call() throws Exception { /*模拟耗时操作,需要5秒*/
Thread.sleep(5000); /*返回一盒做好的月饼编号*/ return new Random().nextInt(10000); } };
/*开启线程B--消费者:获取月饼*/ Runnable runnable=new Runnable() { public void run() { try
{ ExecutorService tPool = Executors.newSingleThreadExecutor(); System.out
.println("老板,给我开始做月饼..."); /*启动线程A--生产者:运行耗时操作,生产月饼 *同时获得一张月饼券CookTicket*/
//final Future<Integer> CookTicket = tPool.submit(callable);
FutureTask<Integer> CookTicket =new FutureTask<Integer>(callable);
tPool.submit(CookTicket);//CookTicket.run();另一种调用方式 //new
Thread(CookTicket).run();另一种调用方式 /*拿到月饼*/ System.out.println(
"5秒钟后用月饼券兑换到月饼,该盒月饼编号:"+CookTicket.get()); System.out.println("拿饼回家..."); }
catch (InterruptedException | ExecutionException e) { e.printStackTrace(); } }
};new Thread(runnable).start(); } }
FutureTask有一个方法 void done()会在每个线程执行完成return结果时回调。
假现在需要实现每个线程完成任务执行后主动执行后续任务。
public class FutureDemo { public static void main(String[] args) { /*
定义生产者:用来做月饼的Callable */ final Callable<Integer> callable = new
Callable<Integer>() {public Integer call() throws Exception { /*模拟耗时操作,需要5秒*/
Thread.sleep(5000); /*返回一盒做好的月饼编号*/ return new Random().nextInt(10000); } };
/*开启线程B--消费者:获取月饼*/ Runnable runnable = new Runnable() { public void run() {
System.out.println("老板,给我开始做月饼..."); /*用缓存线程池同时开多个线程工作*/ ExecutorService tPool
= Executors.newCachedThreadPool();/*启动线程A--生产者:运行耗时操作,三条生产线开始生产月饼*/ for(int i=0
;i<3;i++){ FutureTask<Integer> CookTicket = new FutureTask<Integer>(callable){
/*当某个线程完成任务后,马上回调done函数,执行消费任务。*/ protected void done() { super.done(); try {
/*get()是提取结果的方法*/ System.out.println("5秒钟后用月饼券兑换到月饼,该盒月饼编号:"+get()); } catch
(InterruptedException e) { e.printStackTrace(); }catch (ExecutionException e) {
e.printStackTrace(); } } }; tPool.submit(CookTicket);//new
Thread(CookTicket).run(); } } }; new Thread(runnable).start(); } }
FutureTask在高并发环境下确保任务只执行一次

网上有篇例子,但是中间讲的不是很清楚。我重新梳理了一下。

在很多高并发的环境下,往往我们只需要某些任务只执行一次。这种使用情景FutureTask的特性恰能胜任。举一个例子,假设有一个带key的连接池,当key存在时,即直接返回key对应的对象;当key不存在时,则创建连接。对于这样的应用场景,通常采用的方法为使用一个Map对象来存储key和连接池对应的对应关系,典型的代码如下面所示:
private Map<String, Connection> connectionPool = new HashMap<String,
Connection>();private ReentrantLock lock = new ReentrantLock(); public
ConnectiongetConnection(String key){ try{ lock.lock(); if
(connectionPool.containsKey(key)){return connectionPool.get(key); } else{ //创建
Connection Connection conn = createConnection(); connectionPool.put(key, conn);
return conn; } } finally{ lock.unlock(); } }

在上面的例子中,我们通过加锁确保高并发环境下的线程安全,也确保了connection只创建一次,然而却牺牲了性能。改用ConcurrentHash的情况下,几乎可以避免加锁的操作,性能大大提高。
private ConcurrentHashMap<String,Connection> connectionPool = new
ConcurrentHashMap<String,Connection>();public Connection getConnection(String
key){ Connection conn=connectionPool.get(key); if(conn!=null){ return conn; }
else { conn=createConnection(); Connection return_conn =
connectionPool.putIfAbsent(key,conn);//根据putIfAbsent的返回值判断是否有线程抢先插入了 if
(return_conn!=null){ conn=return_conn; } } return conn; } //创建Connection private
ConnectioncreateConnection(){ return null; }
但是在高并发的情况下有可能出现Connection被创建多次的现象。

为什么呢?因为创建Connection是一个耗时操作,假设多个线程涌入getConnection方法,都发现key对应的键不存在,于是所有涌入的线程都开始执行
conn=createConnection()
,只不过最终只有一个线程能将connection插入到map里。但是这样以来,其它线程创建的的connection就没啥价值,浪费系统开销。

这时最需要解决的问题就是当key不存在时,创建Connection的动作(conn=createConnection();
)能放在connectionPool.putIfAbsent()之后执行,这正是FutureTask发挥作用的时机,基于ConcurrentHashMap和FutureTask的改造代码如下:
private ConcurrentHashMap<String,FutureTask<Connection>>connectionPool = new
ConcurrentHashMap<String, FutureTask<Connection>>();public Connection
getConnection(String key) throws Exception{
FutureTask<Connection>connectionTask=connectionPool.get(key); if
(connectionTask!=null){ return connectionTask.get(); } else{
Callable<Connection> callable =new Callable<Connection>(){ @Override public
Connectioncall() throws Exception { // TODO Auto-generated method stub return
createConnection(); } }; FutureTask<Connection> newTask =new
FutureTask<Connection>(callable); connectionTask =
connectionPool.putIfAbsent(key, newTask);if(connectionTask==null){
connectionTask = newTask; connectionTask.run(); }return connectionTask.get(); }
}//创建Connection private Connection createConnection(){ return null; }

技术
©2019-2020 Toolsou All rights reserved,
LinkedHashMap基本用法&使用实现简单缓存 dedecms网站被黑 劫持到其他网站如何解决苹果不送充填器耳机真为环保?可能还是为了赚钱吧图片格式转换错误总结-myBatis plus 分页numpy:多维数组的创建用C语言做很简单的飞机游戏Keras保存与加载模型(JSON+HDF5)福布斯中国汽车富豪榜:何小鹏第11 李想第14 李斌第15hive大量小文件处理方法总结