Java21 虛擬線程,極大提升了系統性能!
前言
最近虛擬線程火了,極大提升了系統性能。
但有些小伙伴對進程、線程、協程、虛擬線程之間的區別和聯系還是沒有搞清楚。
今天這篇文章就專門跟大家一起聊聊這個話題,希望對你會有所幫助。
一、進程與線程
有些小伙伴在工作中可能經常聽到"進程"和"線程"這兩個詞,但未必真正理解它們之間的本質區別。
讓我用一個簡單的比喻來解釋:
想象一家大工廠(操作系統):
- 進程就像工廠中的一個獨立車間,每個車間有自己獨立的空間、原料和工具。
- 線程就像車間中的工人,共享車間的資源,協同完成生產任務。
進程:獨立的執行環境
進程是操作系統進行資源分配和調度的基本單位。
每個進程都有自己獨立的地址空間、數據棧、代碼段和其他系統資源。
// Java中創建進程的示例
publicclass ProcessExample {
public static void main(String[] args) throws IOException {
// 啟動一個新的進程(比如打開計算器)
ProcessBuilder processBuilder = new ProcessBuilder("calc.exe");
Process process = processBuilder.start();
System.out.println("進程ID: " + process.pid());
System.out.println("是否存活: " + process.isAlive());
// 等待進程結束
try {
int exitCode = process.waitFor();
System.out.println("進程退出碼: " + exitCode);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}進程的特點:
- 獨立性:每個進程有獨立的地址空間,互不干擾
- 安全性:一個進程崩潰不會影響其他進程
- 開銷大:創建和銷毀進程需要較大的系統開銷
- 通信復雜:進程間通信(IPC)需要特殊的機制
線程:輕量級的執行單元
線程是進程內的執行單元,是CPU調度和執行的基本單位。
一個進程可以包含多個線程,這些線程共享進程的資源。
// Java中創建線程的兩種方式
publicclass ThreadExample {
public static void main(String[] args) {
// 方式1:繼承Thread類
Thread thread1 = new MyThread();
thread1.start();
// 方式2:實現Runnable接口
Thread thread2 = new Thread(new MyRunnable());
thread2.start();
// 方式3:使用Lambda表達式
Thread thread3 = new Thread(() -> {
System.out.println("Lambda線程執行: " + Thread.currentThread().getName());
});
thread3.start();
}
}
class MyThread extends Thread {
@Override
public void run() {
System.out.println("MyThread執行: " + Thread.currentThread().getName());
}
}
class MyRunnable implements Runnable {
@Override
public void run() {
System.out.println("MyRunnable執行: " + Thread.currentThread().getName());
}
}線程的特點:
- 共享資源:同一進程內的線程共享內存空間和系統資源
- 輕量級:創建和銷毀線程的開銷比進程小
- 通信簡單:線程間可以直接讀寫共享數據
- 安全性問題:需要處理線程同步和資源共享問題
二、線程的深入剖析
要真正理解線程,我們需要深入操作系統層面。
現代操作系統通常采用三種線程模型:
1. 用戶級線程(ULT)
用戶級線程完全在用戶空間實現,操作系統不知道它們的存在。線程的創建、調度、同步等都由用戶級的線程庫完成。
優點:
- 線程切換不需要陷入內核態,開銷小
- 調度算法可以由應用程序自定義
- 不依賴于操作系統支持
缺點:
- 一個線程阻塞會導致整個進程阻塞
- 無法利用多核CPU的優勢
2. 內核級線程(KLT)
內核級線程由操作系統內核直接支持和管理。每個內核線程對應一個內核級的調度實體。
優點:
- 一個線程阻塞不會影響其他線程
- 能夠利用多核CPU并行執行
缺點:
- 線程切換需要陷入內核態,開銷較大
- 創建線程需要系統調用
3. 混合模型
現代操作系統通常采用混合模型,將用戶級線程映射到內核級線程上。
Java線程就是這種模型的具體實現。
// Java線程與操作系統線程的對應關系
publicclass ThreadInfoExample {
public static void main(String[] args) {
// 創建多個線程
for (int i = 0; i < 5; i++) {
int threadId = i;
new Thread(() -> {
System.out.println("Java線程: " + Thread.currentThread().getName() +
", 操作系統線程ID: " + getThreadId());
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}).start();
}
}
// 獲取操作系統線程ID(Java 10+)
private static long getThreadId() {
return Thread.currentThread().threadId();
}
}三、協程
有些小伙伴可能聽說過協程(Coroutine),尤其是在Go語言中非常流行。
那么協程和線程有什么區別呢?
協程的本質
協程是一種比線程更加輕量級的執行單元,它由程序員在用戶空間控制調度,而不是由操作系統內核調度。
// Java中可以使用第三方庫實現協程(如Quasar)
// 以下是偽代碼示例,展示協程的概念
publicclass CoroutineExample {
public static void main(String[] args) {
// 創建協程
Coroutine coroutine1 = new Coroutine(() -> {
System.out.println("協程1開始");
Coroutine.yield(); // 主動讓出執行權
System.out.println("協程1繼續");
});
Coroutine coroutine2 = new Coroutine(() -> {
System.out.println("協程2開始");
Coroutine.yield(); // 主動讓出執行權
System.out.println("協程2繼續");
});
// 手動調度協程
coroutine1.run();
coroutine2.run();
coroutine1.run();
coroutine2.run();
}
}協程的特點:
- 極輕量級:一個程序可以輕松創建數十萬個協程
- 協作式調度:由程序員控制調度時機
- 低成本切換:切換不需要陷入內核態
- 同步編程風格:可以用同步的方式編寫異步代碼
協程 vs 線程
為了更清晰地理解協程和線程的區別。
我們先看看執行單元的對比圖:

再看看創建數量的對比圖:
四、虛擬線程
Java 19引入了虛擬線程(Virtual Threads),這是Java并發模型的一次重大革新。
虛擬線程旨在解決傳統平臺線程的局限性。
為什么需要虛擬線程?
有些小伙伴在工作中可能遇到過下面這些的問題。
為了處理大量并發請求,我們創建了大量線程,但很快遇到了瓶頸:
- 線程數量限制:操作系統線程數有限制(通常幾千個)
- 內存開銷大:每個線程都需要分配棧內存(默認1MB)
- 上下文切換開銷:線程切換需要內核參與,開銷較大
虛擬線程的實現原理
虛擬線程是JDK實現的輕量級線程,它們不是由操作系統直接調度,而是由JDK調度到平臺線程(操作系統線程)上執行。
// Java 19+ 虛擬線程使用示例
publicclass VirtualThreadExample {
public static void main(String[] args) throws InterruptedException {
// 創建虛擬線程
Thread virtualThread = Thread.ofVirtual().start(() -> {
System.out.println("虛擬線程執行: " + Thread.currentThread());
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
});
// 等待虛擬線程結束
virtualThread.join();
// 使用虛擬線程處理大量任務
try (var executor = Executors.newVirtualThreadPerTaskExecutor()) {
for (int i = 0; i < 10_000; i++) {
int taskId = i;
executor.submit(() -> {
System.out.println("任務 " + taskId + " 在線程: " + Thread.currentThread());
Thread.sleep(1000);
return taskId;
});
}
}
}
}虛擬線程的優勢
- 輕量級:可以創建數百萬個虛擬線程而不會耗盡資源
- 低成本阻塞:虛擬線程阻塞時不會阻塞平臺線程
- 簡化并發編程:可以用同步的代碼風格編寫高并發程序
- 兼容現有代碼:虛擬線程是Thread的實現,兼容現有API
五、虛擬線程如何工作?
為了真正理解虛擬線程,我們需要深入其工作原理。
虛擬線程的實現基于一個關鍵概念:continuation。
Continuation的概念
Continuation表示一個可暫停和恢復的執行上下文。當虛擬線程執行阻塞操作時,JDK會掛起當前的continuation,并釋放平臺線程去執行其他任務。
// 偽代碼:展示continuation的概念
publicclass ContinuationExample {
public static void main(String[] args) {
ContinuationScope scope = new ContinuationScope("example");
Continuation continuation = new Continuation(scope, () -> {
System.out.println("步驟1");
Continuation.yield(scope); // 暫停執行
System.out.println("步驟2");
Continuation.yield(scope); // 暫停執行
System.out.println("步驟3");
});
// 分步執行
while (!continuation.isDone()) {
System.out.println("開始執行步驟...");
continuation.run();
System.out.println("步驟執行暫停");
}
}
}虛擬線程的調度模型
虛擬線程使用ForkJoinPool作為調度器,將虛擬線程調度到平臺線程上執行。
當一個虛擬線程執行阻塞操作時,調度器會自動將其掛起,并調度其他虛擬線程到平臺線程上執行。

這種調度模型使得少量平臺線程可以高效地執行大量虛擬線程,極大地提高了系統的并發能力。
六、不同場景下的選擇
有些小伙伴可能會問:既然虛擬線程這么強大,是不是應該全部使用虛擬線程呢?其實不然,不同的場景適合不同的并發模型。
1. CPU密集型任務
對于CPU密集型任務(如計算、數據處理),傳統線程可能更合適:
// CPU密集型任務示例
publicclass CpuIntensiveTask {
public static void main(String[] args) {
int processors = Runtime.getRuntime().availableProcessors();
ExecutorService executor = Executors.newFixedThreadPool(processors);
for (int i = 0; i < 100; i++) {
executor.submit(() -> {
// 復雜的計算任務
compute();
});
}
executor.shutdown();
}
private static void compute() {
// 模擬CPU密集型計算
long result = 0;
for (long i = 0; i < 100000000L; i++) {
result += i * i;
}
System.out.println("計算結果: " + result);
}
}2. IO密集型任務
對于IO密集型任務(如網絡請求、數據庫操作),虛擬線程有明顯的優勢:
// IO密集型任務示例 - 使用虛擬線程
publicclass IoIntensiveTask {
public static void main(String[] args) {
try (var executor = Executors.newVirtualThreadPerTaskExecutor()) {
for (int i = 0; i < 10_000; i++) {
executor.submit(() -> {
// 模擬IO操作
String data = httpGet("https://api.example.com/data");
processData(data);
returnnull;
});
}
}
}
private static String httpGet(String url) {
// 模擬HTTP請求
try {
Thread.sleep(100); // 模擬網絡延遲
return"response data";
} catch (InterruptedException e) {
thrownew RuntimeException(e);
}
}
private static void processData(String data) {
// 處理數據
System.out.println("處理數據: " + data);
}
}3. 混合型任務
對于既有CPU計算又有IO操作的任務,可以根據具體情況選擇:
// 混合型任務示例
publicclass MixedTask {
public static void main(String[] args) {
// 對于IO部分使用虛擬線程
try (var ioExecutor = Executors.newVirtualThreadPerTaskExecutor()) {
List<Future<String>> futures = new ArrayList<>();
for (int i = 0; i < 1000; i++) {
futures.add(ioExecutor.submit(() -> {
// IO操作
return fetchData();
}));
}
// 對于CPU密集型部分使用固定線程池
int processors = Runtime.getRuntime().availableProcessors();
ExecutorService cpuExecutor = Executors.newFixedThreadPool(processors);
for (Future<String> future : futures) {
cpuExecutor.submit(() -> {
try {
String data = future.get();
// CPU密集型處理
processDataIntensively(data);
} catch (Exception e) {
e.printStackTrace();
}
});
}
cpuExecutor.shutdown();
}
}
}七、性能對比
為了更直觀地展示不同并發模型的性能差異,我們來看一個簡單的性能測試:
// 性能對比測試
publicclass PerformanceComparison {
privatestaticfinalint TASK_COUNT = 10000;
privatestaticfinalint IO_DELAY_MS = 100;
public static void main(String[] args) throws InterruptedException {
// 測試平臺線程
long startTime = System.currentTimeMillis();
testPlatformThreads();
long platformTime = System.currentTimeMillis() - startTime;
// 測試虛擬線程
startTime = System.currentTimeMillis();
testVirtualThreads();
long virtualTime = System.currentTimeMillis() - startTime;
System.out.println("平臺線程耗時: " + platformTime + "ms");
System.out.println("虛擬線程耗時: " + virtualTime + "ms");
System.out.println("性能提升: " + (double) platformTime / virtualTime + "倍");
}
private static void testPlatformThreads() throws InterruptedException {
ExecutorService executor = Executors.newFixedThreadPool(200);
CountDownLatch latch = new CountDownLatch(TASK_COUNT);
for (int i = 0; i < TASK_COUNT; i++) {
executor.submit(() -> {
try {
Thread.sleep(IO_DELAY_MS); // 模擬IO操作
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
latch.countDown();
}
});
}
latch.await();
executor.shutdown();
}
private static void testVirtualThreads() throws InterruptedException {
try (var executor = Executors.newVirtualThreadPerTaskExecutor()) {
CountDownLatch latch = new CountDownLatch(TASK_COUNT);
for (int i = 0; i < TASK_COUNT; i++) {
executor.submit(() -> {
try {
Thread.sleep(IO_DELAY_MS); // 模擬IO操作
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
latch.countDown();
}
});
}
latch.await();
}
}
}測試結果分析:
- 平臺線程池(200線程):處理10000個任務約50秒
- 虛擬線程:處理10000個任務約1秒
- 性能提升:約50倍
這個測試清楚地展示了虛擬線程在IO密集型場景下的巨大優勢。
總結
經過上面的詳細講解,現在我們來總結一下各種并發模型的適用場景和最佳實踐:
1. 進程 vs 線程 vs 協程 vs 虛擬線程
特性 | 進程 | 線程 | 協程 | 虛擬線程 |
隔離性 | 高 | 低 | 低 | 低 |
創建開銷 | 大 | 中 | 小 | 極小 |
切換開銷 | 大 | 中 | 小 | 小 |
內存占用 | 大 | 中 | 小 | 小 |
并發數量 | 幾十個 | 幾千個 | 幾十萬 | 百萬級 |
適用場景 | 獨立應用 | 通用并發 | 特定語言 | IO密集型 |
2. 選擇指南
- 需要完全隔離:選擇進程(如微服務架構)
- CPU密集型任務:選擇平臺線程池(線程數≈CPU核心數)
- IO密集型任務:選擇虛擬線程(Java 19+)
- 極高并發需求:考慮協程(如Go語言)或虛擬線程
- 現有系統遷移:逐步引入虛擬線程,保持兼容性
3. 最佳實踐
有些小伙伴在工作中使用并發編程時,可以參考以下最佳實踐:
- 避免過度使用線程:不要創建過多平臺線程
- 合理使用線程池:根據任務類型選擇合適的線程池
- 嘗試虛擬線程:在IO密集型場景中嘗試使用虛擬線程
- 監控線程狀態:使用監控工具跟蹤線程使用情況
- 理解業務特性:根據業務需求選擇合適的并發模型
未來展望
虛擬線程是Java并發編程的一次重大飛躍,但它們并不是終點。
隨著硬件技術的發展和應用場景的變化,并發模型還會繼續演進:
- 更好的工具支持:調試、監控工具需要適應虛擬線程
- 更優的調度算法:針對不同場景的智能調度
- 新的編程模型:響應式編程、actor模型等與虛擬線程的結合
- 硬件協同優化:與新一代硬件(如DPU)的協同優化
記?。?/span>沒有最好的并發模型,只有最適合的并發模型。






























