java的FutureTask類


1.FutrueTask概念

FutureTask一個可取消的異步計算,FutureTask 實現了Future的基本方法,提空 start cancel 操作,可以查詢計算是否已經完成,並且可以獲取計算的結果。結果只可以在計算完成之后獲取,get方法會阻塞當計算沒有完成的時候,一旦計算已經完成,那么計算就不能再次啟動或是取消。

一個FutureTask 可以用來包裝一個 Callable 或是一個runnable對象。因為FurtureTask實現了Runnable方法,所以一個 FutureTask可以提交(submit)給一個Excutor執行(excution).

2.FutureTask使用場景

FutureTask可用於異步獲取執行結果或取消執行任務的場景。通過傳入Runnable或者Callable的任務給FutureTask,直接調用其run方法或者放入線程池執行,之后可以在外部通過FutureTask的get方法異步獲取執行結果,因此,FutureTask非常適合用於耗時的計算,主線程可以在完成自己的任務后,再去獲取結果。另外,FutureTask還可以確保即使調用了多次run方法,它都只會執行一次Runnable或者Callable任務,或者通過cancel取消FutureTask的執行等。

2.1 FutureTask執行多任務計算場景

利用FutureTask和ExecutorService,可以用多線程的方式提交計算任務,主線程繼續執行其他任務,當主線程需要子線程的計算結果時,在異步獲取子線程的執行結果。

 1 /**
 2  * Created by zhumiao on 2018/8/17.
 3  */
 4 public class FutureTest1 {
 5     public static void main(String[] args) {
 6         Task task = new Task();// 新建異步任務
 7         FutureTask<Integer> future = new FutureTask<Integer>(task) {
 8             // 異步任務執行完成,回調
 9             @Override
10             protected void done() {
11                 try {
12                     System.out.println("future.done():" + get());
13                 } catch (InterruptedException e) {
14                     e.printStackTrace();
15                 } catch (ExecutionException e) {
16                     e.printStackTrace();
17                 }
18             }
19         };
20         // 創建線程池(使用了預定義的配置)
21         ExecutorService executor = Executors.newCachedThreadPool();
22         executor.execute(future);
23 
24         try {
25             Thread.sleep(1000);
26         } catch (InterruptedException e1) {
27             e1.printStackTrace();
28         }
29         // 可以取消異步任務
30 //         future.cancel(true);
31         try {
32             // 阻塞,等待異步任務執行完畢-獲取異步任務的返回值
33             System.out.println("future.get():" + future.get());
34         } catch (InterruptedException e) {
35             e.printStackTrace();
36         } catch (ExecutionException e) {
37             e.printStackTrace();
38         }
39     }
40     // 異步任務
41     static class Task implements Callable<Integer> {
42         // 返回異步任務的執行結果
43         @Override
44         public Integer call() throws Exception {
45             int i = 0;
46             for (; i < 10; i++) {
47                 try {
48                     System.out.println(Thread.currentThread().getName() + "_"
49                             + i);
50                     Thread.sleep(500);
51                 } catch (InterruptedException e) {
52                     e.printStackTrace();
53                 }
54             }
55             return i;
56         }
57     }
58 }
View Code

2.2 FutureTask在高並發下確保任務只執行一次

在很多高並發的環境下,往往我們只需要某些任務只執行一次。這種使用情景FutureTask的特性恰能勝任。舉一個例子,假設有一個帶key的連接池,當key存在時,即直接返回key對應的對象;當key不存在時,則創建連接。對於這樣的應用場景,通常采用的方法為使用一個Map對象來存儲key和連接池對應的對應關系,典型的代碼如下面所示:

 1 private Map<String, Connection> connectionPool = new HashMap<String, Connection>();  
 2 private ReentrantLock lock = new ReentrantLock();  
 3 
 4 public Connection getConnection(String key){  
 5     try{  
 6         lock.lock();  
 7         if(connectionPool.containsKey(key)){  
 8             return connectionPool.get(key);  
 9         }  
10         else{  
11             //創建 Connection  
12             Connection conn = createConnection();  
13             connectionPool.put(key, conn);  
14             return conn;  
15         }  
16     }  
17     finally{  
18         lock.unlock();  
19     }  
20 }  
21 
22 //創建Connection(根據業務需求,自定義Connection)  
23 private Connection createConnection(){  
24     return null;  
25 } 
View Code

在上面的例子中,我們通過加鎖確保高並發環境下的線程安全,也確保了connection只創建一次,然而確犧牲了性能。改用ConcurrentHash的情況下,幾乎可以避免加鎖的操作,性能大大提高,但是在高並發的情況下有可能出現Connection被創建多次的現象。這時最需要解決的問題就是當key不存在時,創建Connection的動作能放在connectionPool之后執行,這正是FutureTask發揮作用的時機,基於ConcurrentHashMap和FutureTask的改造代碼如下:

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

經過這樣的改造,可以避免由於並發帶來的多次創建連接及鎖的出現。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM