多線程是 Java 併發編程的基石。本文將系統梳理線程的兩種創建方式、啟動機制、同步原理及鎖優化策略,並通過大量代碼示例揭示常見誤區,幫助開發者構建穩健的併發程序。
重寫 run() 方法定義任務邏輯,調用 start() 啟動線程。切記:絕不能直接調用 run(),否則會在主線程中同步執行,喪失併發性。start() 內部會完成線程註冊、棧分配等初始化工作,然後由 JVM 調度執行 run()。
public class MyThread extends Thread {
@Override
public void run() { /* 任務代碼 */ }
}
// 使用
new MyThread().start();
將任務邏輯封裝在 run() 中,再將實例傳給 Thread 構造器。此方式解耦任務與線程控制,便於共享數據、避免單繼承限制,也是線程池提交任務的標準形式。
public class MyTask implements Runnable {
@Override
public void run() { /* 任務代碼 */ }
}
// 使用
new Thread(new MyTask()).start();
即使沒有引用變量持有 Thread 對象,JVM 內部仍會註冊線程引用,因此線程對象不會被 GC 回收,直到線程終止。
多線程共享堆內存中的實例變量、靜態變量時,非原子操作(如 i++、long/double 讀寫)會導致數據競爭,產生「臟讀」、「丟失更新」等一致性問題。
this,同一實例的不同線程互斥。Class 對象,所有實例共享此鎖。synchronized(obj) { ... },手動指定鎖對象,粒度更細。對象鎖(非 static synchronized)與類鎖(static synchronized)互不干擾。同一線程可同時持有對象鎖和類鎖;不同線程分別持有對象鎖和類鎖時可並行執行。
僅同步訪問共享變量的核心代碼,而非整個方法。例如:
// 粗粒度:整個方法同步
public synchronized void update() { ... }
// 細粒度:僅同步關鍵區
public void update() {
// 非共享操作...
synchronized(this) { sharedVar++; }
// 非共享操作...
}
細粒度鎖能顯著降低線程爭用,提升併發吞吐量。
在同步塊中重新賦值鎖對象(lock = new Object())會導致每次同步使用不同鎖,完全失效。解決方案:將鎖對象聲明為 final,或使用單例模式獲取固定鎖實例。
基於對象鎖的等待/通知機制:
wait() 釋放鎖並進入等待隊列。notify()(喚醒一個)或 notifyAll()(喚醒全部)。核心規範:wait/notify 必須在 synchronized 塊中調用,否則拋出 IllegalMonitorStateException;建議使用 while(條件不滿足) wait() 防止虛假喚醒。
利用共享鎖 + wait/notify 實現無鎖隊列的線程安全協作:
class Buffer {
final Object lock = new Object();
List<Task> queue = new ArrayList<>();
final int capacity;
public void produce(Task t) throws InterruptedException {
synchronized(lock) {
while(queue.size() == capacity) lock.wait();
queue.add(t);
lock.notifyAll();
}
}
public Task consume() throws InterruptedException {
synchronized(lock) {
while(queue.isEmpty()) lock.wait();
Task t = queue.remove(0);
lock.notifyAll();
return t;
}
}
}
Runnable + 線程池,避免手動管理線程生命週期。final,防止引用變更導致同步失效。while 循環使用,確保條件滿足後再執行。掌握以上核心概念與最佳實踐,是編寫高質量 Java 併發程序的必經之路。