Executor 應用示例
1. 前言
上一節我們學習了 Executor 的基本概念和核心 API,本節帶領大家實現一個具體的應用案例。從實際應用中感受一下 Executor 框架的使用,以及此框架帶來的便利。
本節先描述待實現的案例內容,接著做編碼實現,然后總結使用過程中的注意事項。
2. 案例描述
我們可以通過手工創建線程做邏輯單元的執行,但是當存在大量的需要執行的邏輯單元也是這樣處理,就會出現很多麻煩的事情,且效率非常低下。手工創建線程并做線程管理,需要我們實現很多與業務無關的控制代碼,另外手工不停的創建線程并做線程銷毀,會浪費很多系統資源。
我們在實際項目中,常常通過使用 java 提供好的非常好用的線程框架 Executor 進行任務執行操作。
有這樣一個場景:需要對某個目錄下的所有文件(成百上千)進行加密并用文件的 MD5 串修改文件名稱。
在開始動手實現之前,我們先做一個簡單的分析。在這個案例中,我們將 “對文件進行加密、生成 MD5 串、修改文件名稱” 作為待執行任務的內容。所有文件形成的列表就是我們待處理的數據范圍。為了校驗整個處理過程是否有文件遺漏,我們最終需要核對處理結果。為了方便演示,下面編碼中部分數據采用了模擬的方式生成。
3. 編碼實現
import java.util.ArrayList;
import java.util.List;
import java.util.Random;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
public class ExecutorTest {
// 模擬待處理的文件列表
private static int fileListSize = new Random().nextInt(6);
private static String[] fileList = new String[fileListSize];
static {
for(int i=0; i<fileListSize; i++) {
fileList[i] = "fileName" + i;
}
}
// 主線程
public static void main(String[] args) throws Exception {
// 創建用于處理任務的線程池
ExecutorService executorService = Executors.newFixedThreadPool(10);
// 任務提交,每一個任務處理一個文件
List<FileDealTask> tasks = new ArrayList<>();
for(int i=0; i<fileListSize; i++) {
tasks.add(new FileDealTask(0, fileListSize, fileList[i]));
}
// 等待異步處理結果返回
List<Future<Integer>> results = executorService.invokeAll(tasks);
// 獲取任務執行結果
Integer total = 0;
for (Future<Integer> result : results) {
total = total + result.get();
}
System.out.println("預備處理的文件個數" + fileListSize + ",總共處理的文件個數:" + total);
// 關閉線程池
executorService.shutdown();
}
}
上面代碼注釋已經很清楚了,我們觀察下面的代碼,看看任務代碼。
import java.util.Random;
import java.util.concurrent.Callable;
public class FileDealTask implements Callable<Integer> {
private String fileName;
public FileDealTask(int first, int last, String fileName) {
this.fileName = fileName;
}
@Override
public Integer call() throws Exception {
try {
Thread.sleep(new Random().nextInt(2000));
System.out.println(Thread.currentThread().getName() + ":文件" + fileName + "已處理完畢");
} catch (Exception e) {
return 0;
}
return 1;
}
}
我們通過在 IDE 中運行上面這個示例,看看實際的運行結果。
【補充視頻】
上面代碼邏輯中有隨機內容,每次運行結果會有差異,運行上面的代碼,我們觀察運行結果:
pool-1-thread-2:文件fileName1已處理完畢
pool-1-thread-3:文件fileName2已處理完畢
pool-1-thread-1:文件fileName0已處理完畢
預備處理的文件個數3,總共處理的文件個數:3
和我們的預期一致。
4. 注意事項
- Executors 是 Executor 框架體系中的一個獨立的工具類,用于快速創建各類線程池,在實際應用中,如果需要對線程池的各類參數做更多的自定義,可以參考此類的實現。
- 做好評估權衡,當需要處理的數據量不是特別大時,沒有必要使用 Executor。其底層使用多線程的方式處理任務,涉及到線程上下文的切換,當數據量不大的時候使用串行會比使用多線程快。
- 在使用時,如果主線程不關心子任務的執行結果,請使用 Runnable 接口封裝任務的執行邏輯。
5. 視頻演示
6. 小結
本節通過一個實際例子的編碼實現,展示了 Executor 的具體用法。當然本節中的用法相對比較簡單,更多的用法還需要大家進一步學習,希望大家多思考勤練習,早日掌握之。