併發編程三大核心組件——**線程池、ForkJoinPool、ThreadLocal**——分別解決「任務調度執行」、「分治並行計算」、「線程隔離變量」三類核心問題。本文結合 JDK 源碼設計、實戰代碼、輸出分析與選型指南,構建企業級併發開發的完整知識體系。
| 方法 | 周期計算基準 | 行為差異 | 適用場景 |
| scheduleAtFixedRate | **上一次開始時間** | 固定頻率、任務執行超時可能並發執行 | 固定頻率采集、心跳 |
| scheduleWithFixedDelay | **上一次結束時間** | 固定間隔、串行保證、無並發風險 | 串行處理、資源獨佔任務 |
實戰代碼與輸出分析
Runnable task = () -> {
System.out.println(new Date());
Thread.sleep(3000);
System.out.println(new Date());
};
ScheduledExecutorService ses = Executors.newScheduledThreadPool(1);
// 固定頻率:1s 延遲,5s 周期(從開始時間計算)
ses.scheduleAtFixedRate(task, 1, 5, TimeUnit.SECONDS);
/*
輸出時間軸分析:
T+1s 任務1開始
T+4s 任務1結束 (耗時3s)
T+6s 任務2開始 (1+5=6,非 4+5)
T+9s 任務2結束
T+11s 任務3開始
*/
FixedDelay 輸出對比
ses.scheduleWithFixedDelay(task, 1, 5, TimeUnit.SECONDS);
/*
T+1s 任務1開始
T+4s 任務1結束
T+9s 任務2開始 (4+5=9,從結束時間計算)
T+12s 任務2結束
T+17s 任務3開始
*/
三、ForkJoinPool — 分治並行計算引擎
核心架構
- 工作竊取算法:每個線程維護雙端隊列,**任務拆分後放入自己隊列**,空閒線程**竊取**他人隊列尾部任務
- 適用場景:CPU 密集型、可遞歸分解、無 I/O 阻塞(大數據聚合、圖像處理、排序、搜索)
- 不適用:I/O 密集、同步阻塞、任務粒度過細(調度開銷 > 計算收益)
兩大任務類型
| 類型 | 返回值 | 適用場景 |
| RecursiveAction | void | 純副作用任務(寫文件、更新 DB) |
| RecursiveTask<V> | V | 有返回值聚合任務(求和、查找、MapReduce) |
實戰:並行求和 0-327
public class ForkJoinSum extends RecursiveTask {
private static final int THRESHOLD = 50; // 閾值:小於直接計算
private final int start, end;
public ForkJoinSum(int start, int end) { this.start = start; this.end = end; }
@Override
protected Long compute() {
if (end - start < THRESHOLD) {
long sum = 0;
for (int i = start; i < end; i++) sum += i;
return sum;
}
int mid = (start + end) >>> 1;
ForkJoinSum left = new ForkJoinSum(start, mid);
ForkJoinSum right = new ForkJoinSum(mid, end);
left.fork(); // 異步執行左半部分
return right.compute() + left.join(); // 右半部分當前線程計算 + 等待左半部分
}
}
// 使用
ForkJoinPool pool = ForkJoinPool.commonPool(); // 共享池,JVM 級
Long result = pool.invoke(new ForkJoinSum(0, 327));
System.out.println("Sum: " + result); // 53301
關鍵優化點
- 閾值調優:太小 → 任務開銷大;太大 → 並行度低。經驗值:任務耗時 1-10ms
- 避免阻塞:fork/join 內部不得有 I/O、鎖、sleep
- 異常處理:
get() / join() 拋出 ExecutionException,需統一捕獲
- 共享池 vs 獨立池:
commonPool() 節省資源,但業務隔離建議自建
四、ThreadLocal — 線程隔離變量神器
核心原理
- ThreadLocalMap 存儲在 **Thread 實例** 中,Key 為 ThreadLocal 弱引用,Value 為任意對象
- **每個線程獨立副本**,互不干擾,無需同步
- **內存洩漏風險**:Key 弱引用 → ThreadLocal 可被 GC,但 Value 強引用 → 若線程長期存活(線程池),Entry 殘留 → **必須手動 remove()**
三大核心方法
void set(T value) // 設置當前線程副本
T get() // 獲取當前線程副本(首次返回 null)
void remove() // 刪除當前線程副本(防洩漏,必須調用!)
實戰:用戶上下文傳遞
public class UserContextHolder {
private static final ThreadLocal CONTEXT = new ThreadLocal<>();
public static void set(User user) { CONTEXT.set(user); }
public static User get() { return CONTEXT.get(); }
public static void clear() { CONTEXT.remove(); } // 必須調用!
}
// Filter/Interceptor 中
UserContextHolder.set(currentUser);
try {
// 業務邏輯可隨處獲取 UserContextHolder.get()
} finally {
UserContextHolder.clear(); // 防止線程池線程復用導致數據洩漏
}
經典輸出演示
public class ThreadLocalDemo {
static class Account {
private ThreadLocal name = new ThreadLocal<>();
public Account(String init) { name.set(init); }
public String getName() { return name.get(); }
public void setName(String n) { name.set(n); }
}
static class MyThread extends Thread {
private Account account;
public MyThread(Account a) { this.account = a; }
public void run() {
for (int i = 0; i < 5; i++) {
if (i == 3) account.setName(getName());
System.out.println("Thread: " + getName() + ", Account: " + account.getName() + ", i=" + i);
}
}
}
public static void main(String[] args) {
Account a = new Account("初始名字");
new MyThread(a).start();
new MyThread(a).start();
}
}
/*
輸出分析:
構造時的賬戶名:初始名字
Thread-0, Account: null, i=0 ← 未set前為null
Thread-1, Account: null, i=0
Thread-0, Account: null, i=1
Thread-1, Account: null, i=1
Thread-0, Account: null, i=2
Thread-1, Account: null, i=2
Thread-0, Account: Thread-0, i=3 ← 各線程獨立副本
Thread-0, Account: Thread-0, i=4
Thread-1, Account: Thread-1, i=3
Thread-1, Account: Thread-1, i=4
*/
進階:InheritableThreadLocal — 父子線程傳遞
- 子線程創建時**自動複製**父線程值
- 適用:鏈路追蹤 TraceId、權限上下文傳遞
- 注意:線程池復用場景**不生效**(非父子關係),需配合
TransmittableThreadLocal (Alibaba TTL)
五、選型決策總表
| 場景 | 推薦方案 | 核心理由 |
| 常規業務異步處理 | 自定義 ThreadPoolExecutor + 有界隊列 + CallerRunsPolicy | 可控、可監控、背壓保護 |
| 定時/周期任務 | ScheduledThreadPoolExecutor / Spring @Scheduled / XXL-JOB | 標準化、持久化、集群支持 |
| CPU 密集型分治計算 | ForkJoinPool / CompletableFuture + 自定義執行器 | 工作竊取、無鎖、遞歸分解 |
| 請求級上下文傳遞 | ThreadLocal + Filter/Interceptor 清理 | 零參數傳遞、線程隔離 |
| 父子線程上下文傳遞 | InheritableThreadLocal / TransmittableThreadLocal | 自動傳遞、線程池兼容 |
| 高併發 I/O (JDK 21+) | Virtual Threads (Executors.newVirtualThreadPerTaskExecutor()) | 輕量級、阻塞不佔平台線程 |
六、避坑指南與最佳實踐
- 線程池:拒絕 Executors 默認工廠,**務必有界隊列**、命名線程、監控隊列積壓、優雅關閉
shutdown() + awaitTermination()
- ForkJoin:閾值科學設定、禁阻塞、異常聚合處理、區分 commonPool 與獨立池
- ThreadLocal:**try-finally 必調用 remove()**、靜態 final 修飾、避免大對象、線程池場景配合 TTL
- 監控指標:activeCount、queueSize、taskCount、completedTaskCount、rejectCount、keepAliveTime
- 上一篇文章: Java 核心基石:String 不可變性原理、內存模型與實戰陷阱全解析
- 下一篇文章: JVM 記憶體結構深度解析:基於《深入理解 Java 虛擬機》學習筆記
Copyright ©
程式碼之家 All Rights Reserved