Thread(線程)
線程 = 程序執行的最小單位(一個進程至少有一個線程)。線程內有自己的執行棧、程序計數器(PC),但與同進程內其他線程共享堆內存與進程資源
在java中,線程由java.lang.Thread表示,但我們通常不直接操作裸線程,而用更高層的并發工具(線程池、任務、并發集合等)
一、線程的生命周期與常見操作
狀態:NEW、RUNNABLE、BLOCKED / WAITING / TIMED_WAITING、TERMINATED
- NEW:對象創建后,未start()
- RUNNABLE:可運行(包括實際運行或等待運行)
- BLOCKED:等待獲取對象監視器(synchronized)
- WAITING:等待其他線程通知(Object.wait()、Thread.join()無超時)
- TERMINATED:run方法返回或拋出異常結束
常用方法:
- start():啟動線程(不要直接調用run,會變成普通方法調用)
- join():等待線程結束
- sleep():當前線程休眠(會釋放CPU,但不釋放鎖)
- interrupt():中斷線程(合作式,需要線程內檢查isInterrupted()或捕獲InterruptedException)
- yield():禮讓(建議少用,平臺依賴)
二、創建線程
- 繼承Thread
// 問題:單繼承限制、職責不清(把任務和線程綁定)
class MyThread extends Thread {public void run() { /* 任務 */ }
}
new MyThread().start();
- 實現Runnable
// 好處:解耦任務和線程、可復用
Runnable task = () -> { /* 任務 */ };
new Thread(task).start();
- 實現Callable + Future(用于返回值 / 異常)
// 支持返回值和拋異常
Callable<Integer> c = () -> 1+2;
Future<Integer> f = executor.submit(c);
Integer result = f.get(); // 阻塞取結果
- 使用線程池(ExecutorService / ThreadPoolExecutor)
ExecutorService pool = Executors.newFixedThreadPool(5);
pool.submit(() -> { /* 任務 */ });
pool.shutdown();
// 不要濫用Executor.*生成的未定制池(可能使用無界隊列或者不合適的場景)一般直接構造ThreadPoolExecutor并調參
- ForkJoinPool(分治、并行流)
- 適合CPU密集型、可分解任務(RecursiveTask)。采用工作竊取(work-stealing)
- CompletableFuture(異步組合)
- 異步流式編程supplyAsync、鏈式 thenApply、異常處理 exceptionally,能把異步任務組合成復雜流程。
- 虛擬線程(Project Loom —— 概念)
- 新一代思想:大量“輕量級線程”由 JVM 管理,降低線程/請求的成本,能用同步編程模型寫大并發程序。
- (提示:具體語法/可用性隨 JDK 版本變化,寫生產代碼前確認 JDK 支持情況。)
三、同步與內存可見性
并發核心問題是:共享可變狀態會導致競態、可見性問題
-
synchronized(內置鎖)
- 監視器(monitor):synchronized修飾方法或代碼塊
- 可重入(同一線程可重復獲得同一把鎖)
- wait() / notify() / notifyAll()在持鎖狀態下使用,用于線程間協作
// 特點:保證同一時刻只有一個線程能執行臨界區代碼,并且進入 / 退出時會觸發內存可見性更新/*單例模式(懶漢式加鎖) */ public class Singleton {private static Singleton instance;public static synchronized Singleton getInstance() {if (instance == null) {instance = new Singleton();}return instance;} }/*多線程安全更新共享資源 */ public class Counter {private int count = 0;public synchronized void increment() {count++;}public synchronized int getCount() {return count;} }/*保證集合安全(eg:ArrayList在多線程下不安全,可用Collections.synchronizedList包裝) */
-
volatile
- 保證可見性(寫入一個volatile變量后,后續讀能看到最新值)和禁止指令重排序(對單個變量)
- 不能替代鎖來保證復合操作的原子性(比如count++不是原子性)
// 特點:讓一個線程修改的變量能立即被其他線程看到;禁止指令重排序。 // 適合 狀態標志、開關類變量 /*線程停止標志 */ class Task implements Runnable {private volatile boolean running = true;public void run() {while (running) {// do work...}}public void stop() {running = false; // 立刻被其他線程看到} } /*配置熱更新一個后臺線程定時刷新配置,把值寫入volatile變量,業務線程能馬上讀到新值 */// 注意:下面這種情況要用synchronized或AtomicInteger
-
Lock(java.util.concurrent.locks)
- ReentrantLock:比synchronized更靈活(可中斷獲取鎖、可輪詢、可超時),支持Condition(類似多個wait / nottify集合)
- ReadWriteLock:讀寫分離鎖,讀多寫少場景有利
// 顯示鎖,比synchronized更靈活,可定時、可中斷、支持多個條件隊列 /*嘗試獲取鎖(避免死等) */ ReentrantLock lock = new ReentrantLock(); if (lock.tryLock(1, TimeUnit.SECONDS)) {try {// 臨界區} finally {lock.unlock();} } else {// 獲取鎖失敗,執行降級邏輯 }/* 讀寫分離(適合讀多寫少) */ ReadWriteLock rwLock = new ReentrantReadWriteLock(); Lock r = rwLock.readLock(); Lock w = rwLock.writeLock();
-
原子類(AtomicInteger / Long / Reference)
- 基于CAS(無鎖算法),用于計數等簡單場景。注意ABA問題(可用AtomicStampedReference)
// 基于CAS實現的無鎖并發,常用于計數器、序列號 /*高性能計數器 */ private AtomicInteger count = new AtomicInteger(); public void increment() {count.incrementAndGet(); }/*并發限流 / 統計QPS、在線人數統計,避免用synchronized降低性能 */
-
內存模型(JMM)要點
- 主內存 + 工作內存(線程本地緩存),寫到主內存后其他線程可見。volatile / 鎖會產生內存屏障,保證可見性與有序性
四、常用并發工具(java.util.concurrent)
- 線程池:ThreadoolExecutor(核心、最大線程數、隊列、飽和策略)
- 阻塞隊列:ArrayBlockingQueue,LinkBlockingQueue,PriorityBlockQueue(常用于生產者-消費者)
- 同步輔助類:
CountDownLatch
,CyclicBarrier
,Semaphore
,Phaser
,Exchanger
- 并發集合:
ConcurrentHashMap
,CopyOnWriteArrayList
,ConcurrentLinkedQueue
- ForkJoinPool(并行任務)
- BlockingQueue + ThreadPool:典型生產者-消費者模式
- FutureTask:可作為Runnable也可獲取結果,常用于把Callable封裝成任務
五、線程安全問題與常見陷阱
- 競態條件(race condition):并發修改同一狀態導致錯誤結果
- 死鎖(deadlock):兩個或多個線程互相持有對方需要的鎖。四要素:互斥、持有并等待、不可剝奪、循環等待
- 活鎖(livelock):線程不斷執行但無法取得進展(一直讓步)
- 饑餓(starvation):某線程長期無法獲得CPU或鎖資源
- 優先級反轉(priority inversion):低優先級線程持鎖導致高優先級線程等待
- 內存泄露(ThreadLocal):線程池中使用ThreadLocal若不remove(),會造成對象無法回收(尤其在容器應用中)
排查工具:jstack(線程dump)、jvisualvm、jconsole、應用日志、線程名 / ID打點、Flight Recorder。線程dump是定位死鎖 / 阻塞的利器
六、守護線程(Daemon)vs 用戶線程(User)
- 用戶線程:只要存在任意用戶線程,JVM不會退出
- 守護線程:JVM在所有用戶線程結束后會退出,即使守護線程還在跑