FutureTask可用於非同步獲取執行結果或取消執行任務的場景。通過傳入Runnable或者Callable的任務給FutureTask,直接呼叫其run方法或者放入執行緒池執行,之後可以在外部通過FutureTask的get方法非同步獲取執行結果,因此,FutureTask非常適合用於耗時的計算,主執行緒可以在完成自己的任務後,再去獲取結果。另外,FutureTask還可以確保即使呼叫了多次run方法,它都只會執行一次Runnable或者Callable任務,或者通過cancel取消FutureTask的執行等。
1.執行多工計算
FutureTask執行多工計算的使用場景
利用FutureTask和ExecutorService,可以用多執行緒的方式提交計算任務,主執行緒繼續執行其他任務,當主執行緒需要子執行緒的計算結果時,在非同步獲取子執行緒的執行結果。
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<FutureTask<Integer>>();
// 建立執行緒池
ExecutorService exec = Executors.newFixedThreadPool(5);
for (int i = 0; i < 10; i++) {
// 傳入Callable物件建立FutureTask物件
FutureTask<Integer> ft = new FutureTask<Integer>(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) {
result = iniResult;
this.taskName = taskName;
System.out.println("生成子執行緒計算任務: " + taskName);
}
public String getTaskName() {
return this.taskName;
}
@Override
public Integer call() throws Exception {
// TODO Auto-generated method stub
for (int i = 0; i < 100; i++) {
result = +i;
}
// 休眠5秒鐘,觀察主執行緒行為,預期的結果是主執行緒會繼續執行,到要取得FutureTask的結果是等待直至完成。
Thread.sleep(5000);
System.out.println("子執行緒計算任務: " + taskName + " 執行完成!");
return result;
}
}
}
複製程式碼
生成子執行緒計算任務: 0
生成子執行緒計算任務: 1
生成子執行緒計算任務: 2
生成子執行緒計算任務: 3
生成子執行緒計算任務: 4
生成子執行緒計算任務: 5
生成子執行緒計算任務: 6
生成子執行緒計算任務: 7
生成子執行緒計算任務: 8
生成子執行緒計算任務: 9
所有計算任務提交完畢, 主執行緒接著幹其他事情!
子執行緒計算任務: 0 執行完成!
子執行緒計算任務: 2 執行完成!
子執行緒計算任務: 3 執行完成!
子執行緒計算任務: 4 執行完成!
子執行緒計算任務: 1 執行完成!
子執行緒計算任務: 8 執行完成!
子執行緒計算任務: 7 執行完成!
子執行緒計算任務: 6 執行完成!
子執行緒計算任務: 9 執行完成!
子執行緒計算任務: 5 執行完成!
多工計算後的總結果是:990
複製程式碼
2.高併發環境下
FutureTask在高併發環境下確保任務只執行一次
在很多高併發的環境下,往往我們只需要某些任務只執行一次。這種使用情景FutureTask的特性恰能勝任。舉一個例子,假設有一個帶key的連線池,當key存在時,即直接返回key對應的物件;當key不存在時,則建立連線。對於這樣的應用場景,通常採用的方法為使用一個Map物件來儲存key和連線池對應的對應關係,典型的程式碼如下面所示:
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
Connection conn = createConnection();
connectionPool.put(key, conn);
return conn;
}
} finally {
lock.unlock();
}
}
//建立Connection
private Connection createConnection() {
return null;
}
複製程式碼
在上面的例子中,我們通過加鎖確保高併發環境下的執行緒安全,也確保了connection只建立一次,然而確犧牲了效能。改用ConcurrentHash的情況下,幾乎可以避免加鎖的操作,效能大大提高,但是在高併發的情況下有可能出現Connection被建立多次的現象。這時最需要解決的問題就是當key不存在時,建立Connection的動作能放在connectionPool之後執行,這正是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 Connection call() 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;
}
複製程式碼
經過這樣的改造,可以避免由於併發帶來的多次建立連線及鎖的出現。
Contact
- 作者:鵬磊
- 出處:www.ymq.io
- Email:admin@souyunku.com
- 版權歸作者所有,轉載請註明出處
- Wechat:關注公眾號,搜雲庫,專注於開發技術的研究與知識分享