1.任務
把程式抽象成多個任務。
2.現代web程式劃分任務邊界
以獨立的客戶請求為邊界。就是一個請求一個任務。
3.任務排程策略
3.1序列的執行
糟糕的響應性和吞吐量。
3.2為每一個任務建立一個執行緒
結論:
- 任務交由子執行緒處理,提高了響應性和吞吐量。
- 任務處理的程式碼必須是執行緒安全的。
不足:
- 執行緒生命週期的開銷非常高。
- 資源消耗。可執行執行緒多於可用處理器的數量,會有執行緒閒置佔用記憶體,且大量執行緒競爭CPU時將產生其他效能開銷。
- 穩定性。不同平臺可建立執行緒的數量有限制。
4.Java中Executor框架的設計
4.1設計理念
Java提供了Executor框架來執行任務。基於生產者-消費者模式。提交任務就是操作相當於生產者,執行任務的執行緒相當於消費者。(解耦,削峰)
public interface Executor {
void execute(Runnable command);
}
複製程式碼
4.2執行策略
任務的提交程式碼散佈在整個程式的業務程式碼中。
執行策略則統一交由框架處理。
執行策略中定義了任務執行的"What,Where,When,How"等方面,包括:
- 在什麼(What)執行緒中執行任務?
- 任務按照什麼(What)順序執行(優先順序)?
- 有多少個(How Many)任務能併發執行?
- 在佇列中有多少個(How Many)任務在等待執行?
- 系統該怎麼(How)拒絕任務?
- 在任務執行前後,應該進行哪些(What)動作?
通過將任務提交與任務的執行策略分離,有助於在部署階段選擇與可用硬體資源最匹配的執行策略。
4.3執行緒池
Executor任務執行框架將"為每一個任務分配一個執行緒"策略程式設計基於執行緒池的策略。
類庫提供了一個靈活的執行緒池及一些有用的預設配置。如newFixedThreadpool。
- Web伺服器不會再高負載情況下失敗。
- 但是任務到達的速度總是超過任務執行的速度,伺服器仍有可能耗盡記憶體。
4.4Executor的生命週期
Executor擴充套件了ExecutorService介面,新增了一些用於生命週期管理的方法。
public interface ExecutorService extends Executor {
/**
* 平緩的關閉過程:不再接受新任務,等待已經提交的任務執行完成。
*/
void shutdown();
/**
* 粗暴的關閉過程:它將嘗試取消所有執行中的任務,不在啟動佇列中尚未開始執行的任務。
*/
list<Runnable> shutdownNow();
boolean isShutdown();
boolean isTerminated();
boolean awaitTermination(long timeout, TimeUnit unit);
}
複製程式碼
4.5延遲任務和週期任務
JAVA中提供Timer來管理定時任務。
- Timer執行定時任務只會建立一個執行緒。
- Timer是基於絕對時間的排程機制,對系統時間敏感。
- Timer存線上程洩露問題(Timer不捕獲異常,當丟擲一個未檢查異常時執行緒將終止)。
ScheduledThreadPoolExecutor更優質的管理定時任務。
- 其內部是一個執行緒池。
- 其很好的解決了Timer的執行緒洩露問題。
不適用於分散式環境。
5.找出可利用的並行性
本章提供一些示例來發掘在一個請求中的並行性。
5.1 示例:序列的頁面渲染器
假設頁面 = 文字標籤 + 圖片
如下程式碼序列的執行渲染。
public class SingleThreadRenderer {
void renderPage(CharSequence source) {
renderText(source);
List<ImageData> imageData = new ArrayList<ImageData>();
for (ImageInfo imageInfo : scanForImageInfo(source))
imageData.add(imageInfo.downloadImage());
for (ImageData data : imageData)
renderImage(data);
}
}
複製程式碼
5.2攜帶結果的任務Callable與Future
Runnable作為基本的任務表現形式。缺陷:1.無返回值。2.不能丟擲一個受檢查異常。
- Callable介面
它是任務更好的抽象,描述了一個任務的返回值和異常。
public interface Callable<V> {
V call() throws Exception;
}
複製程式碼
- Future介面
它表示一個任務的生命週期,並提供了相應的方法來判斷任務是否已經完成或取消。
public interface Future<V> {
boolean cancel(boolean mayInterruptIfRunning);
boolean isCancelled();
boolean isDone();
V get() throws Exception;
V get(long timeout, TimeUnit unit);
}
複製程式碼
5.3示例:使用Future實現頁面渲染器
將渲染過程分解成兩個任務,一個是渲染所有的文字,一個是下載所有影像。
程式碼略。
複製程式碼
渲染文字和渲染圖片併發執行。
5.4在異構任務並行化中存在的侷限
上例中一般渲染文字的速度遠遠高於渲染圖片的速度,程式最終和序列執行效率差別不大,程式碼確變得更復雜了。
只有大量相互獨立且同構的任務可以併發進行處理時,才能體現出效能的提升。
5.5CompletionService:Executor與BlockingQueue
提交一組任務,簡單的寫法。
@Test
public void test() throws Exception{
ExecutorService executor = Executors.newFixedThreadPool(5);
List<Future<String>> futures = new ArrayList();
for (int i=0; i<5; i++){
final int param = i;
Future<String> future = executor.submit(new Callable<String>() {
@Override
public String call() throws Exception {
Thread.sleep(param * 1000);
return "result" + param;
}
});
futures.add(future);
}
for (int i=4; i>0; i--) {
System.out.println(futures.get(i).get());
}
}
複製程式碼
CompletionService將Executor和BlockingQueue的功能融合。你可以將Callable任務提交給它執行,然後使用類似佇列操作的take和poll方法來獲得已完成的結果。
5.6示例:使用CompletionService實現頁面渲染器
書上的示例:略。
@Test
public void test() throws Exception{
ExecutorService executor = Executors.newFixedThreadPool(5);
CompletionService<String> completionService = new ExecutorCompletionService<>(executor);
for (int i=4; i>0; i--){
final int param = i;
completionService.submit(new Callable<String>() {
@Override
public String call() throws Exception {
Thread.sleep(param * 1000);
return "result" + param;
}
});
}
for (int i=0; i<4; i++) {
System.out.println(completionService.take().get());
}
}
輸出:
result1
result2
result3
result4
複製程式碼
5.7為任務設定時限
為單個任務設定時間。
@Test
public void singleTaskTest(){
ExecutorService executor = Executors.newFixedThreadPool(5);
Future<String> future = executor.submit(new Callable<String>() {
@Override
public String call() {
try {
Thread.sleep(2000L);
}catch (Exception e){
e.printStackTrace();
}
System.out.println("任務執行完畢...");
return "singleTask.";
}
});
try {
System.out.println(future.get(1, TimeUnit.SECONDS));
}catch (TimeoutException e){
System.out.println("任務超時...");
future.cancel(true); // 這句話的是否登出影響執行情況,原理未知?
}catch (InterruptedException e){
e.printStackTrace();
}catch (ExecutionException e){
e.printStackTrace();
}
}
複製程式碼
5.8示例:陸行預定入口網站
未多個任務設定超時時間。
6.總結
本章主要是介紹了Java的Executor框架的優點和一些常見需求。
還有對任務的劃分粒度,要根據業務場景分析任務邊界。