Thread-Per-Message
January 10, 2022你寫了一個簡單的下載網頁程式:
import java.io.*;
import java.net.URI;
import java.net.http.*;
import java.net.http.HttpResponse.BodyHandlers;
import java.nio.file.*;
import static java.nio.file.StandardCopyOption.*;
public class Main {
static InputStream openStream(String uri) throws Exception {
return HttpClient
.newHttpClient()
.send(
HttpRequest.newBuilder(URI.create(uri)).build(),
BodyHandlers.ofInputStream()
)
.body();
}
static void download(String uri, String fileName) throws Exception {
Files.copy(
openStream(uri), Paths.get(fileName), REPLACE_EXISTING);
}
public static void main(String[] args) throws Exception {
String[] uris = {
"https://openhome.cc/zh-tw/algorithm/",
"https://openhome.cc/zh-tw/computation/",
"https://openhome.cc/zh-tw/toy-lang/",
"https://openhome.cc/zh-tw/pattern/"
};
for(var uri : uris) {
download(
uri,
uri.replace("https://openhome.cc/zh-tw/", "")
.replace("/", "")
.concat(".html")
);
}
}
}
一個請求一個執行緒
在單執行緒程式下,需要等上個請求完成,才能執行下個請求,你想了一下,下載後是直接存檔,不用取得 download
方法的傳回值,由於網路連線會有等待回應等空檔,若這時能再開啟下個請求,應該可以加快程式的執行。
for(var uri : uris) {
new Thread(() -> {
try {
download(
uri,
uri.replace("https://openhome.cc/zh-tw/", "")
.replace("/", "")
.concat(".html")
);
} catch (Exception e) {
throw new RuntimeException(e);
}
}).start();
}
一個請求一個執行緒,這就是 Thread-Per-Message,在執行緒的入門文件,應該都會有類似的範例吧!或者是寫桌面圖形介面時,為了避免視窗在操作某些輸入輸出時凍結,那些輸入輸出請求,通常都會另建一個執行緒來處理。
簡單到我都在想,這個名詞就不用介紹算了…XD
封裝執行緒的取得
不過呢!建立執行緒是要成本的,視窗程式之類的,偶而建立一下執行緒來完成輸入輸出,或者一些會阻斷的操作,是沒有關係,畢竟使用者的手速應該不致於快到,讓執行緒的建立成本產生負擔。
不過,若是一些自動化請求,或者是伺服器,每個請求就建立一個執行緒,那就要考量建立執行緒的成本了!比較好的方式是,封裝執行緒的取得方式,
class ThreadService {
static void submit(Runnable runnable) {
new Thread(runnable).start();
}
}
很簡單的封裝概念!如此一來,客戶端就可以使用 ThreadService.submit
:
for(var uri : uris) {
ThreadService.submit(() -> {
try {
download(
uri,
uri.replace("https://openhome.cc/zh-tw/", "")
.replace("/", "")
.concat(".html")
);
} catch (Exception e) {
throw new RuntimeException(e);
}
});
}
或許將來,你會進一步考量到重用執行緒之類的問題,那就修改 ThreadService.submit
就好,或者你會更進一步地,考量到各種不同的執行緒服務需求,像是執行緒排程等,你可能重構 ThreadService
之類…或者其實你早就知道,要是使用 Java 的話,標準 API 就提供了 Executor
這類框架。
Thread-Per-Message 就行為上應該解釋為,為每個請求「安排」一個執行緒,然而應該進一步考量到,對客戶端隱藏安排的細節,因為為每個請求各自安排執行緒,並不是只有新建執行緒這個選項!