對synchronized理解&思考

在 Java 并發(fā)編程中,synchronized是最基礎(chǔ)也最常用的線程安全保障手段。它看似簡單,實則背后藏著 JVM 的復(fù)雜優(yōu)化邏輯,且在不同 JDK 版本中經(jīng)歷了多次性能升級。今天我們就從 “實現(xiàn)原理”“版本差異”“工程作用” 三個維度,徹底吃透synchronized。

一、synchronized 的底層實現(xiàn):不止是 “鎖”

synchronized的核心是通過 “互斥” 保證臨界區(qū)代碼的原子執(zhí)行,但其實現(xiàn)依賴 JVM 的對象頭監(jiān)視器鎖(Monitor) 機制,不同修飾對象的實現(xiàn)邏輯略有差異。

1. 鎖的載體:對象頭里的 “鎖狀態(tài)”

Java 中所有對象都有一個 “對象頭”(Object Header),其中Mark Word字段存儲了對象的鎖狀態(tài)信息,這是synchronized實現(xiàn)的關(guān)鍵。

默認情況下,Mark Word存儲對象哈希碼、GC 年齡等信息;當對象被加鎖時,Mark Word會更新為 “鎖指針”,指向?qū)?yīng)的鎖結(jié)構(gòu)(如偏向鎖記錄、輕量級鎖棧幀)。

2. 兩種鎖模式的實現(xiàn)邏輯

synchronized根據(jù)修飾對象不同,鎖的載體也不同:

  • 修飾普通方法 / 代碼塊:鎖載體是 “對象實例”(普通方法鎖this,代碼塊鎖自定義對象);

  • 修飾靜態(tài)方法:鎖載體是 “類對象”(如XXX.class),因為靜態(tài)方法屬于類而非實例。

無論哪種模式,最終都會關(guān)聯(lián)到監(jiān)視器鎖(Monitor) ——JVM 中的一種同步機制,每個對象 / 類都對應(yīng)一個 Monitor。當線程進入synchronized代碼塊時,會嘗試 “獲取 Monitor”;執(zhí)行完后 “釋放 Monitor”,其他線程需等待 Monitor 釋放才能競爭。

二、JDK 版本差異:從 “重量級鎖” 到 “鎖升級” 的性能躍遷

synchronized的性能在 JDK 1.6 前后有天壤之別,核心原因是 JDK 1.6 引入了 “鎖升級” 機制,通過偏向鎖→輕量級鎖→重量級鎖的漸進式升級,平衡 “線程安全” 與 “性能開銷”。

| JDK 版本 | 核心優(yōu)化 | 鎖機制特點 | 適用場景 |

| ----------- | -------------- | -------------------------------------- | -------------- |

| JDK 1.5 及之前 | 無 | 僅支持 “重量級鎖”,依賴操作系統(tǒng)內(nèi)核態(tài)互斥量(Mutex),線程切換成本高 | 極少并發(fā)場景(性能差) |

| JDK 1.6 及之后 | 鎖升級、CAS 算法、自旋鎖 | 支持三種鎖狀態(tài),按需升級,避免頻繁內(nèi)核態(tài)切換 | 全場景覆蓋(低并發(fā)→高并發(fā)) |

鎖升級的完整流程(JDK 1.6+):

  1. 偏向鎖:當只有一個線程反復(fù)進入臨界區(qū)時,JVM 會在Mark Word中記錄該線程 ID,后續(xù)該線程進入時無需競爭,直接獲取鎖(幾乎無開銷);

  2. 輕量級鎖:當有第二個線程競爭鎖時,偏向鎖升級為輕量級鎖。線程會通過CAS(Compare and Swap) 嘗試修改Mark Word的鎖指針,若成功則獲取鎖,失敗則自旋(循環(huán)重試);

  3. 重量級鎖:當自旋次數(shù)超過閾值(默認 10 次),或更多線程參與競爭時,輕量級鎖升級為重量級鎖。此時線程會阻塞并進入內(nèi)核態(tài)等待,由操作系統(tǒng)調(diào)度喚醒(開銷最高,但適合高并發(fā))。

這種 “能不加鎖就不加鎖,能輕鎖就不輕轉(zhuǎn)重” 的設(shè)計,讓synchronized在低并發(fā)場景下性能接近無鎖,高并發(fā)場景下保證安全。

三、工程開發(fā)中的核心作用:解決并發(fā) “痛點”

在實際項目中,synchronized是應(yīng)對線程安全問題的 “基礎(chǔ)工具”,主要解決以下三類核心場景:

1. 保證原子操作:避免 “中間態(tài)” 數(shù)據(jù)錯亂

當多個線程執(zhí)行 “多步操作”(如 “判斷庫存→扣減庫存”“讀取計數(shù)→累加計數(shù)”)時,synchronized能保證這些操作成為 “不可分割的原子塊”,避免其他線程在中間步驟打斷。

工程實例:電商秒殺中的庫存扣減,用synchronized包裹 “判斷庫存是否大于 0→扣減庫存” 的邏輯,防止超賣。

2. 保證可見性:避免 “緩存不一致”

線程修改共享變量后,若未及時刷新到主內(nèi)存,其他線程可能讀取到舊值(緩存不一致)。synchronized在釋放鎖時會強制刷新緩存,讓修改后的變量同步到主內(nèi)存;獲取鎖時會清空本地緩存,從主內(nèi)存重新讀取變量,從而保證可見性。

工程實例:多線程控制的 “服務(wù)開關(guān)”,用synchronized修飾開關(guān)的修改與讀取邏輯,確保線程能立即感知開關(guān)狀態(tài)變化。

3. 保證有序性:避免 “指令重排序”

JVM 為優(yōu)化性能會重排序指令,可能導(dǎo)致代碼執(zhí)行順序與預(yù)期不一致(如單例模式中 “對象未初始化完成就賦值給引用”)。synchronized會禁止臨界區(qū)內(nèi)的指令重排序,同時保證 “一個線程釋放鎖后,另一個線程獲取鎖前” 的操作不會交叉,從而保證有序性。

工程實例:非 volatile 修飾的單例模式,用synchronized修飾實例創(chuàng)建邏輯,避免指令重排序?qū)е碌目罩羔槷惓!?/p>

四、工程使用的 “避坑” 建議

  1. 縮小鎖粒度:盡量修飾代碼塊而非整個方法,只鎖定 “真正需要同步的邏輯”(如庫存扣減只鎖扣減部分,而非整個商品查詢方法),減少線程阻塞時間;

  2. 避免鎖對象變化:鎖對象(如this、自定義對象)不能是 “可變對象”(如用 String 作為鎖對象,可能因字符串常量池復(fù)用導(dǎo)致鎖沖突),建議用private final Object lock = new Object();定義不可變鎖對象;

  3. 不嵌套鎖:避免在synchronized代碼塊中嵌套另一個synchronized(如 “鎖 A” 中調(diào)用 “鎖 B” 的同步方法),防止死鎖;

  4. 高并發(fā)場景慎用:若并發(fā)量極高(如每秒萬級請求),synchronized的重量級鎖可能成為性能瓶頸,此時可結(jié)合 “并發(fā)容器(如 ConcurrentHashMap)”“原子類(如 AtomicInteger)” 等更輕量的方案優(yōu)化。

五、總結(jié)

synchronized從 JDK 1.5 的 “重量級鎖” 進化到 JDK 1.6 + 的 “智能鎖升級”,早已不是 “性能殺手”,而是工程開發(fā)中 “簡單可靠” 的并發(fā)解決方案。它的核心價值在于用較低的理解成本,解決原子性、可見性、有序性三大并發(fā)痛點,是 Java 開發(fā)者必須掌握的基礎(chǔ)工具。

在實際項目中,無需盲目追求復(fù)雜的并發(fā)框架,先學好synchronized的底層邏輯與使用場景,才能在 “安全” 與 “性能” 之間找到平衡,寫出更穩(wěn)定的并發(fā)代碼。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容