JUC组件扩展(一):FutureTask理解

一、概述

FutureTask包装器是一种非常便利的机制,同时实现了Future和Runnable接口。

类图如下:

FutureTask是一种可以取消的异步的计算任务。它的计算是通过Callable实现的,它等价于可以携带结果的Runnable,并且有三个状态:等待、运行和完成。完成包括所有计算以任意的方式结束,包括正常结束、取消和异常。

Future有个get方法而获取结果只有在计算完成时获取,否则会一直阻塞直到任务转入完成状态,然后会返回结果或者抛出异常。

FutureTask有下面几个重要的方法:

1.get()

阻塞一直等待执行完成拿到结果

2.get(int timeout, TimeUnit timeUnit)

阻塞一直等待执行完成拿到结果,如果在超时时间内,没有拿到抛出异常

3.isCancelled()

是否被取消

4.isDone()

是否已经完成

5.cancel(boolean mayInterruptIfRunning)

试图取消正在执行的任务

二、FutureTask的状态转换过程

* NEW -> COMPLETING -> NORMAL
 * NEW -> COMPLETING -> EXCEPTIONAL
 * NEW -> CANCELLED
 * NEW -> INTERRUPTING -> INTERRUPTED

三、FutureTask的执行过程

创建一个futureTask对象task
提交task到调度器executor等待调度或者在另外一个线程中执行task

等待调度中...

如果此时currentThread调取执行结果task.get(),会有几种情况
if task 还没有被executor调度或正在执行中
    阻塞当前线程,并加入到一个阻塞链表中waitNode
else if task被其它Thread取消,并取消成功 或task处于中断状态
    throw exception
else if task执行完毕,返回执行结果,或执行存在异常,返回异常信息
    
        
如果此时有另外一个线程调用task.get()
    
执行过程同上

四、应用场景

1. Future用于异步获取执行结果或者取消任务。

2. 在高并发场景下确保任务只执行一次。

五、源码分析

1.核心状态

 1  /**
 2  * The run state of this task, initially NEW.  The run state
 3  * transitions to a terminal state only in methods set,
 4  * setException, and cancel.  During completion, state may take on
 5  * transient values of COMPLETING (while outcome is being set) or
 6  * INTERRUPTING (only while interrupting the runner to satisfy a
 7  * cancel(true)). Transitions from these intermediate to final
 8  * states use cheaper ordered/lazy writes because values are unique
 9  * and cannot be further modified.
10  * 
11  * Possible state transitions:
12  * NEW -> COMPLETING -> NORMAL
13  * NEW -> COMPLETING -> EXCEPTIONAL
14  * NEW -> CANCELLED
15  * NEW -> INTERRUPTING -> INTERRUPTED
16  */
17 private volatile int state;
18 private static final int NEW          = 0;
19 private static final int COMPLETING   = 1;
20 private static final int NORMAL       = 2;
21 private static final int EXCEPTIONAL  = 3;
22 private static final int CANCELLED    = 4;
23 private static final int INTERRUPTING = 5;
24 private static final int INTERRUPTED  = 6;

2.构造函数

 1 public FutureTask(Callable<V> callable) {
 2     if (callable == null)
 3         throw new NullPointerException();
 4     this.callable = callable;
 5     this.state = NEW;       // ensure visibility of callable
 6 }
 7     
 8 public FutureTask(Runnable runnable, V result) {
 9     this.callable = Executors.callable(runnable, result);
10     this.state = NEW;       // ensure visibility of callable
11 }

3.获取执行结果

 1 public V get() throws InterruptedException, ExecutionException {
 2     int s = state;
 3     if (s <= COMPLETING)
 4         s = awaitDone(false, 0L);
 5     return report(s);
 6 }
 7 
 8 public V get(long timeout, TimeUnit unit)
 9     throws InterruptedException, ExecutionException, TimeoutException {
10     if (unit == null)
11         throw new NullPointerException();
12     int s = state;
13     if (s <= COMPLETING &&
14         (s = awaitDone(true, unit.toNanos(timeout))) <= COMPLETING)
15         throw new TimeoutException();
16     return report(s);
17 }

4.执行方法

 1 public void run() {
 2     if (state != NEW ||
 3         !UNSAFE.compareAndSwapObject(this, runnerOffset,
 4                                      null, Thread.currentThread()))
 5         return;
 6     try {
 7         Callable<V> c = callable;
 8         if (c != null && state == NEW) {
 9             V result;
10             boolean ran;
11             try {
12                 result = c.call();
13                 ran = true;
14             } catch (Throwable ex) {
15                 result = null;
16                 ran = false;
17                 setException(ex);
18             }
19             if (ran)
20                 set(result);
21         }
22     } finally {
23         // runner must be non-null until state is settled to
24         // prevent concurrent calls to run()
25         runner = null;
26         // state must be re-read after nulling runner to prevent
27         // leaked interrupts
28         int s = state;
29         if (s >= INTERRUPTING)
30             handlePossibleCancellationInterrupt(s);
31     }
32 }

5.设置状态

 1 protected void set(V v) {
 2     if (UNSAFE.compareAndSwapInt(this, stateOffset, NEW, COMPLETING)) {
 3         outcome = v;
 4         UNSAFE.putOrderedInt(this, stateOffset, NORMAL); // final state
 5         finishCompletion();
 6     }
 7 }
 8 
 9 protected void setException(Throwable t) {
10     if (UNSAFE.compareAndSwapInt(this, stateOffset, NEW, COMPLETING)) {
11         outcome = t;
12         UNSAFE.putOrderedInt(this, stateOffset, EXCEPTIONAL); // final state
13         finishCompletion();
14     }
15 }

六、代码示例

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

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

 1 public class FutureTaskForMultiCompute {
 2     public static void main(String[] args) throws InterruptedException, ExecutionException {
 3         // 创建任务集合  
 4         List<FutureTask<Integer>> taskList = new ArrayList<FutureTask<Integer>>();
 5         // 创建线程池  
 6         ExecutorService exec = Executors.newFixedThreadPool(5);
 7         for (int i = 0; i < 10; i++) {
 8             FutureTask<Integer> ft = new FutureTask<>(new ComputeTask(i, ""+i));
 9             taskList.add(ft);
10             // 提交给线程池执行任务,也可以通过exec.invokeAll(taskList)一次性提交所有任务;
11             exec.submit(ft);
12         }
13         
14         System.out.println("所有计算任务提交完毕, 主线程接着干其他事情!");  
15         
16         // 开始统计各计算线程计算结果  
17         Integer totalResult = 0;  
18         for (FutureTask<Integer> ft : taskList) {  
19             totalResult += ft.get();
20         }
21         
22         // 关闭线程池  
23         exec.shutdown();  
24         System.out.println("多任务计算后的总结果是:" + totalResult);  
25     }
26     
27 }
28 class ComputeTask implements Callable<Integer> {
29 
30     private Integer result = 0;  
31     private String taskName = "";  
32     
33     public String getTaskName(){  
34         return this.taskName;  
35     }
36       
37     public ComputeTask(Integer iniResult, String taskName){  
38         result = iniResult;  
39         this.taskName = taskName;  
40         System.out.println("生成子线程计算任务: "+taskName);  
41     }  
42     
43     @Override
44     public Integer call() throws Exception {
45         for (int i = 0; i < 100; i++) {  
46             result =+ i;  
47         }
48         // 休眠5秒钟,观察主线程行为,预期的结果是主线程会继续执行,到要取得FutureTask的结果是等待直至完成。  
49         Thread.sleep(5000);  
50         System.out.println("子线程计算任务: "+taskName+" 执行完成!");  
51         return result;  
52     }
53     
54 }

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

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

 1 public class FutureTaskTest {
 2     private Map<String, Connection> connectionPool = new HashMap<String, Connection>(); 
 3     private ReentrantLock lock = new ReentrantLock();  
 4     
 5     public Connection getConnection(String key){
 6         try {
 7             lock.lock();
 8             if(connectionPool.containsKey(key)){  
 9                 return connectionPool.get(key);  
10             }else{
11                 //创建 Connection  
12                 Connection conn = createConnection();  
13                 connectionPool.put(key, conn);  
14                 return conn;  
15             }
16         } finally{
17             lock.unlock();  
18         }
19         
20     }
21     
22     //创建Connection  
23     private Connection createConnection(){  
24         return null;  
25     }  
26     
27 }

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

 1 public class FutureTaskTest {
 2     private ConcurrentHashMap<String,FutureTask<Connection>>connectionPool = new ConcurrentHashMap<String, FutureTask<Connection>>();  
 3       
 4     public Connection getConnection(String key) throws Exception{  
 5         FutureTask<Connection> connectionTask = connectionPool.get(key);  
 6         if(connectionTask!=null){  
 7             return connectionTask.get();  
 8         }  
 9         else{  
10             Callable<Connection> callable = new Callable<Connection>(){  
11                 @Override  
12                 public Connection call() throws Exception {  
13                     // TODO Auto-generated method stub  
14                     return createConnection();  
15                 }  
16             };  
17             FutureTask<Connection> newTask = new FutureTask<Connection>(callable);  
18             connectionTask = connectionPool.putIfAbsent(key, newTask);  
19             if(connectionTask==null){  
20                 connectionTask = newTask;  
21                 connectionTask.run();  
22             }  
23             return connectionTask.get();  
24         }  
25     }  
26       
27     //创建Connection  
28     private Connection createConnection(){  
29         return null;  
30     }  
31     
32 }

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

posted @ 2018-10-11 19:54  夏威夷8080  阅读(617)  评论(0编辑  收藏  举报