在Java中,synchronized
關鍵字是實現線程同步的核心工具,用于保證同一時刻只有一個線程可以執行被修飾的代碼塊或方法。以下從基本原理、鎖升級過程、應用場景及優化建議四個維度詳細解析:
一、基本原理
1. 同步的對象
synchronized
鎖的是對象而非代碼:
- 修飾實例方法:鎖的是當前實例對象(
this
); - 修飾靜態方法:鎖的是類的Class對象(如
MyClass.class
); - 修飾代碼塊:鎖的是括號中的對象(如
synchronized(obj)
)。
2. 底層實現
- JVM層面:通過對象頭中的Mark Word和**Monitor(監視器)**實現。
對象頭的Mark Word包含鎖狀態位(如偏向鎖、輕量級鎖、重量級鎖)和指向Monitor的指針。 - 字節碼層面:
- 同步方法:通過
ACC_SYNCHRONIZED
標志位標識; - 同步代碼塊:通過
monitorenter
和monitorexit
指令實現。
- 同步方法:通過
3. 互斥鎖語義
- 線程進入同步塊前需獲取Monitor的所有權(鎖),退出時釋放鎖;
- 未獲取到鎖的線程會被阻塞,進入Monitor的等待隊列。
二、鎖升級的具體過程
JDK 6之后,synchronized
鎖機制進行了優化,引入鎖升級(偏向鎖 → 輕量級鎖 → 重量級鎖)以減少性能開銷。
1. 偏向鎖(Biased Locking)
- 適用場景:單線程環境,無鎖競爭。
- 原理:
- 首次線程訪問同步塊時,Mark Word存儲該線程ID(偏向狀態);
- 后續該線程再次進入同步塊時,無需CAS操作,直接獲取鎖;
- 若其他線程嘗試競爭鎖,偏向鎖撤銷,升級為輕量級鎖。
- 優點:無CAS操作,僅首次獲取鎖時有少量開銷。
- 缺點:存在鎖撤銷(偏向鎖撤銷需STW),若存在多線程競爭,反而增加開銷。
2. 輕量級鎖(Lightweight Locking)
- 適用場景:多線程交替執行同步塊(無實際競爭)。
- 原理:
- 線程進入同步塊時,JVM在當前線程棧幀中創建鎖記錄(Lock Record);
- 通過CAS將對象頭的Mark Word復制到鎖記錄,并將Mark Word指向鎖記錄地址;
- 若CAS成功,獲取輕量級鎖;若失敗,說明存在競爭,鎖膨脹為重量級鎖。
- 優點:競爭不激烈時,避免線程阻塞,減少用戶態/內核態切換。
- 缺點:競爭激烈時,頻繁CAS導致性能下降。
3. 重量級鎖(Heavyweight Lock)
- 適用場景:多線程同時競爭鎖。
- 原理:
- 鎖膨脹后,Mark Word指向操作系統的Monitor對象;
- 未獲取到鎖的線程被阻塞(進入內核態),放入Monitor的等待隊列;
- 鎖釋放時,喚醒等待隊列中的線程(需從內核態轉回用戶態)。
- 缺點:線程阻塞/喚醒涉及用戶態與內核態切換,性能開銷大。
4. 鎖升級流程圖
無鎖狀態 → 偏向鎖(單線程) → 輕量級鎖(多線程交替) → 重量級鎖(多線程競爭)
5. 鎖升級的觸發條件
- 偏向鎖撤銷:當其他線程嘗試訪問偏向鎖對象時,JVM在安全點(Safepoint)撤銷偏向鎖;
- 輕量級鎖膨脹:線程嘗試CAS獲取輕量級鎖失敗時,鎖膨脹為重量級鎖;
- 批量重偏向/撤銷:當一個類的對象頻繁發生偏向鎖撤銷時,JVM會對該類的對象批量重偏向或撤銷。
三、應用場景
1. 保護共享資源
- 示例:多線程操作同一個計數器:
public class Counter {private int count = 0;// 同步方法,鎖的是thispublic synchronized void increment() {count++;} }
2. 實現原子操作
- 示例:雙重檢查鎖定(DCL)實現單例模式:
public class Singleton {private static volatile Singleton instance; // 必須用volatile禁止指令重排public static Singleton getInstance() {if (instance == null) { // 第一次檢查synchronized (Singleton.class) { // 鎖的是Class對象if (instance == null) { // 第二次檢查instance = new Singleton();}}}return instance;} }
3. 保證復合操作的原子性
- 示例:檢查再執行(Check-Then-Act)操作:
public class Inventory {private Map<String, Integer> stock = new HashMap<>();// 復合操作:先檢查庫存,再扣減public synchronized boolean decreaseStock(String product, int amount) {if (stock.getOrDefault(product, 0) >= amount) {stock.put(product, stock.get(product) - amount);return true;}return false;} }
4. 線程間協作(wait/notify機制)
- 示例:生產者-消費者模型:
public class ProducerConsumer {private final Queue<Integer> queue = new LinkedList<>();private final int CAPACITY = 10;// 生產者方法public synchronized void produce(int item) throws InterruptedException {while (queue.size() == CAPACITY) {wait(); // 等待隊列有空間}queue.add(item);notifyAll(); // 通知消費者有新元素}// 消費者方法public synchronized int consume() throws InterruptedException {while (queue.isEmpty()) {wait(); // 等待隊列有元素}int item = queue.poll();notifyAll(); // 通知生產者有空間return item;} }
四、優化建議
1. 縮小同步塊范圍
- 反例:
public synchronized void process() {// 非關鍵代碼long startTime = System.currentTimeMillis();// 關鍵代碼(需要同步)synchronized (this) {// 操作共享資源}// 非關鍵代碼System.out.println("耗時:" + (System.currentTimeMillis() - startTime)); }
- 正例:僅同步關鍵代碼:
public void process() {long startTime = System.currentTimeMillis();// 僅同步關鍵代碼synchronized (this) {// 操作共享資源}System.out.println("耗時:" + (System.currentTimeMillis() - startTime)); }
2. 優先使用同步代碼塊而非同步方法
- 同步方法默認鎖的是
this
,可能導致鎖范圍過大; - 同步代碼塊可精確控制鎖對象。
3. 使用細粒度鎖替代粗粒度鎖
- 反例:
public class Bank {private Map<String, Account> accounts = new HashMap<>();// 粗粒度鎖:整個方法同步public synchronized void transfer(String from, String to, double amount) {// 轉賬邏輯} }
- 正例:
public class Bank {private Map<String, Account> accounts = new HashMap<>();// 細粒度鎖:僅鎖相關賬戶public void transfer(String from, String to, double amount) {Account fromAccount = accounts.get(from);Account toAccount = accounts.get(to);// 按順序加鎖,避免死鎖Account first = fromAccount.hashCode() < toAccount.hashCode() ? fromAccount : toAccount;Account second = first == fromAccount ? toAccount : fromAccount;synchronized (first) {synchronized (second) {// 轉賬邏輯}}} }
4. 考慮替代方案
- 原子類(如
AtomicInteger
)替代簡單計數器的synchronized
; - 讀寫鎖(
ReentrantReadWriteLock
)替代讀多寫少場景的synchronized
; - 并發容器(如
ConcurrentHashMap
)替代synchronized Map
。
五、總結
synchronized
關鍵字的核心特點:
- 優點:
- 使用簡單,無需手動釋放鎖;
- 鎖升級機制在不同場景下有較好的性能表現;
- 支持線程間協作(wait/notify)。
- 缺點:
- 無法中斷正在等待鎖的線程;
- 不支持超時獲取鎖;
- 鎖粒度較粗(要么全鎖,要么全放)。
適用場景:
- 簡單的同步需求(如保護共享資源、實現原子操作);
- 需配合wait/notify實現線程間協作。
在高并發場景下,若性能敏感,可考慮使用ReentrantLock
、StampedLock
等更靈活的鎖機制。