java?synchronized關(guān)鍵字用法和底層原理舉例詳解
一、synchronized的用法
synchronized 關(guān)鍵字用于實(shí)現(xiàn)線程同步,確保多個(gè)線程在訪問(wèn)共享資源時(shí)不會(huì)發(fā)生數(shù)據(jù)競(jìng)爭(zhēng)和不一致的問(wèn)題。它主要有三種使用方式:
1. 同步實(shí)例方法
public synchronized void method() {
// 同步代碼
}
- 鎖對(duì)象是當(dāng)前實(shí)例(
this)。 - 同一個(gè)實(shí)例的多個(gè)線程調(diào)用此方法時(shí)會(huì)互斥。
2. 同步靜態(tài)方法
public static synchronized void staticMethod() {
// 同步代碼
}
- 鎖對(duì)象是當(dāng)前類的 Class 對(duì)象(如
MyClass.class)。 - 所有實(shí)例的線程調(diào)用此方法時(shí)都會(huì)互斥。
3. 同步代碼塊
synchronized (lockObject) {
// 同步代碼
}
- 可以靈活指定鎖對(duì)象(可以是任意對(duì)象)。
- 縮小了同步范圍,提高性能。
4. 深入:wait(),notify(),notifyAll()
這三個(gè)方法是定義在 Object 類中的本地方法,必須在一個(gè)同步代碼塊或同步方法內(nèi)(即已經(jīng)持有該對(duì)象的監(jiān)視器鎖)調(diào)用,否則會(huì)拋出 IllegalMonitorStateException 異常。它們與 synchronized 配合使用,實(shí)現(xiàn)線程間的協(xié)調(diào)(等待/通知機(jī)制)。
wait(): 使當(dāng)前線程釋放其持有的對(duì)象鎖,并進(jìn)入等待狀態(tài)(WAITING),直到其他線程調(diào)用該對(duì)象的notify()或notifyAll()方法,或被中斷。調(diào)用后,線程會(huì)釋放鎖。notify(): 隨機(jī)喚醒一個(gè)正在等待該對(duì)象鎖的線程。被喚醒的線程會(huì)從等待池移動(dòng)到阻塞隊(duì)列(EntryList)中,等待鎖釋放后重新競(jìng)爭(zhēng)鎖。notifyAll(): 喚醒所有正在等待該對(duì)象鎖的線程。這些線程都會(huì)被移動(dòng)到阻塞隊(duì)列中,共同競(jìng)爭(zhēng)鎖。
典型的生產(chǎn)者-消費(fèi)者模式示例:
public class WaitNotifyExample {
private final Object lock = new Object();
private boolean conditionMet = false;
public void consumer() throws InterruptedException {
synchronized (lock) {
// 使用while循環(huán)防止"虛假喚醒"
while (!conditionMet) {
lock.wait(); // 釋放lock鎖,當(dāng)前線程進(jìn)入等待
}
// 條件滿足,執(zhí)行消費(fèi)任務(wù)
System.out.println("Consuming...");
conditionMet = false;
lock.notifyAll(); // 消費(fèi)完成,通知生產(chǎn)者
}
}
public void producer() throws InterruptedException {
synchronized (lock) {
while (conditionMet) {
lock.wait(); // 如果條件已滿足,則等待消費(fèi)者消費(fèi)
}
// 生產(chǎn)任務(wù)
System.out.println("Producing...");
conditionMet = true;
lock.notifyAll(); // 生產(chǎn)完成,通知消費(fèi)者
}
}
}
關(guān)鍵點(diǎn):
- 總是需要在循環(huán)中調(diào)用
wait()以防止虛假喚醒(Spurious Wakeup),即線程可能在沒有被通知、中斷或超時(shí)的情況下醒來(lái)。 wait()會(huì)釋放鎖,而sleep()不會(huì)。- 通常更推薦使用
notifyAll(),因?yàn)?notify()是隨機(jī)喚醒,可能導(dǎo)致某些線程永遠(yuǎn)無(wú)法被喚醒(線程饑餓)。
二、底層原理
synchronized 的底層實(shí)現(xiàn)依賴于 JVM 內(nèi)部的 監(jiān)視器鎖(Monitor) 機(jī)制,主要涉及以下概念:
1. 對(duì)象頭與 Mark Word
- 在 HotSpot 虛擬機(jī)中,每個(gè)對(duì)象都有一個(gè)對(duì)象頭,其中包含 Mark Word(標(biāo)記字段)。
- Mark Word 的長(zhǎng)度為 32位 或 64位,用于存儲(chǔ)對(duì)象的哈希碼、分代年齡、鎖狀態(tài)標(biāo)志位等信息。鎖的狀態(tài)就記錄在這里。
2. 鎖升級(jí)的詳細(xì)過(guò)程
為了平衡性能與安全性,JVM 對(duì) synchronized 進(jìn)行了優(yōu)化,引入了鎖升級(jí)機(jī)制。這個(gè)過(guò)程是單向的:無(wú)鎖 → 偏向鎖 → 輕量級(jí)鎖 → 重量級(jí)鎖。
無(wú)鎖狀態(tài) (01)
- 初始狀態(tài),對(duì)象剛被創(chuàng)建,尚未有任何線程競(jìng)爭(zhēng)。
偏向鎖 (Biased Locking) (01)
- 目的:消除在無(wú)競(jìng)爭(zhēng)情況下的同步開銷。假設(shè)在大多數(shù)情況下,鎖不僅不存在多線程競(jìng)爭(zhēng),而且總是由同一線程多次獲得。
- 流程:
- 加鎖:當(dāng)一個(gè)線程第一次訪問(wèn)同步塊時(shí),它會(huì)通過(guò) CAS 操作將自己的 線程ID 寫入對(duì)象頭的 Mark Word。如果成功,則進(jìn)入偏向模式,鎖標(biāo)志位變?yōu)?
01,且偏向模式標(biāo)志為1。 - 執(zhí)行:此后,只要這個(gè)線程再次進(jìn)入同步塊,只需檢查 Mark Word 中的線程ID是否是自己。如果是,則無(wú)需任何同步操作(如CAS、系統(tǒng)互斥)直接執(zhí)行,極大提升性能。
- 撤銷:一旦出現(xiàn)另一個(gè)線程來(lái)嘗試競(jìng)爭(zhēng)鎖,偏向鎖就會(huì)撤銷(Revoke Bias)。撤銷過(guò)程需要等待全局安全點(diǎn)(STW),然后檢查原持有偏向鎖的線程是否仍活躍。如果活躍,則鎖升級(jí)為輕量級(jí)鎖;如果已不活躍,則可將對(duì)象置為無(wú)鎖狀態(tài),重新偏向新的線程。
- 加鎖:當(dāng)一個(gè)線程第一次訪問(wèn)同步塊時(shí),它會(huì)通過(guò) CAS 操作將自己的 線程ID 寫入對(duì)象頭的 Mark Word。如果成功,則進(jìn)入偏向模式,鎖標(biāo)志位變?yōu)?
輕量級(jí)鎖 (Lightweight Locking) (00)
- 目的:在有多線程競(jìng)爭(zhēng),但競(jìng)爭(zhēng)時(shí)間錯(cuò)開(即近乎交替執(zhí)行)的場(chǎng)景下,避免線程直接進(jìn)入阻塞,減少用戶態(tài)到內(nèi)核態(tài)的切換開銷。
- 加鎖流程:
- 線程在執(zhí)行同步塊之前,JVM 會(huì)先在當(dāng)前線程的棧幀中創(chuàng)建一個(gè)名為鎖記錄(Lock Record) 的空間。
- 將對(duì)象頭的 Mark Word 復(fù)制到線程的鎖記錄中(稱為 Displaced Mark Word)。
- 線程嘗試通過(guò) CAS 操作將對(duì)象頭的 Mark Word 替換為指向其鎖記錄的指針。
- 如果成功,當(dāng)前線程獲得鎖,鎖標(biāo)志位變?yōu)?
00。 - 如果失敗,表示至少有一條線程與當(dāng)前線程競(jìng)爭(zhēng),當(dāng)前線程會(huì)嘗試自旋(循環(huán)重試CAS操作)來(lái)獲取鎖。
- 解鎖流程:
- 使用 CAS 操作將 Displaced Mark Word 替換回對(duì)象頭。
- 如果成功,表示沒有競(jìng)爭(zhēng)發(fā)生。
- 如果失敗,表示當(dāng)前鎖存在競(jìng)爭(zhēng),鎖會(huì)膨脹為重量級(jí)鎖,并在解鎖時(shí)喚醒等待的線程。
- 自旋優(yōu)化:線程不會(huì)立即阻塞,而是循環(huán)嘗試獲取鎖。避免了線程上下文切換的開銷,但會(huì)消耗CPU。如果自旋過(guò)度(如自旋次數(shù)超過(guò)閾值,或競(jìng)爭(zhēng)的線程數(shù)過(guò)多),鎖會(huì)直接升級(jí)為重量級(jí)鎖。
重量級(jí)鎖 (Heavyweight Locking) (10)
- 目的:處理高并發(fā)、激烈競(jìng)爭(zhēng)的場(chǎng)景。
- 流程:當(dāng)輕量級(jí)鎖競(jìng)爭(zhēng)失敗后,會(huì)膨脹為重量級(jí)鎖。此時(shí),Mark Word 中存儲(chǔ)的是指向一個(gè)互斥量(Mutex) 的指針,這個(gè)互斥量由操作系統(tǒng)內(nèi)核提供。
- 所有等待鎖的線程都會(huì)進(jìn)入阻塞狀態(tài)(BLOCKED),不再消耗CPU。鎖的獲取和釋放需要操作系統(tǒng)進(jìn)行線程的掛起和喚醒,這個(gè)過(guò)程涉及到復(fù)雜的用戶態(tài)到內(nèi)核態(tài)的切換,開銷最大。
鎖升級(jí)狀態(tài)總結(jié)表:
| 鎖狀態(tài) | 存儲(chǔ)內(nèi)容 | 標(biāo)志位 | 適用場(chǎng)景 |
|---|---|---|---|
| 無(wú)鎖 | 對(duì)象的哈希碼、分代年齡等 | 01 | 無(wú)競(jìng)爭(zhēng) |
| 偏向鎖 | 持有偏向鎖的線程ID、Epoch、分代年齡、偏向模式標(biāo)志 1 | 01 | 只有一個(gè)線程訪問(wèn)同步塊 |
| 輕量級(jí)鎖 | 指向棧中鎖記錄的指針 | 00 | 多個(gè)線程交替訪問(wèn),低并發(fā)競(jìng)爭(zhēng) |
| 重量級(jí)鎖 | 指向互斥量(Mutex)的指針 | 10 | 高并發(fā),激烈競(jìng)爭(zhēng) |
3. 底層指令
- 同步代碼塊通過(guò)
monitorenter和monitorexit指令實(shí)現(xiàn):- 進(jìn)入同步塊時(shí)執(zhí)行
monitorenter,退出時(shí)執(zhí)行monitorexit(包括正常退出和異常退出)。
- 進(jìn)入同步塊時(shí)執(zhí)行
- 同步方法通過(guò)方法表中的
ACC_SYNCHRONIZED標(biāo)志標(biāo)識(shí),JVM通過(guò)此標(biāo)志判斷是否需要同步。
三、注意事項(xiàng)
- 可重入性:
synchronized是可重入鎖,同一線程可多次獲取同一把鎖。 - 鎖對(duì)象不能為 null:同步代碼塊的鎖對(duì)象不能是
null,否則會(huì)拋出NullPointerException。 - 性能考慮:
- 盡量減小同步范圍(使用代碼塊而非整個(gè)方法)。
- 避免鎖競(jìng)爭(zhēng)激烈,否則會(huì)升級(jí)為重量級(jí)鎖,降低性能。
- 與 Lock 的區(qū)別:
synchronized是關(guān)鍵字,JVM 級(jí)別實(shí)現(xiàn);Lock是 API 級(jí)別實(shí)現(xiàn)。Lock更靈活(可中斷、超時(shí)、公平鎖等),但需手動(dòng)釋放鎖。
wait/notify使用注意:- 必須在同步塊內(nèi)調(diào)用。
- 使用
while循環(huán)檢查條件,防止虛假喚醒。 - 明確鎖對(duì)象,調(diào)用
obj.wait()時(shí)必須持有obj的鎖。
四、示例代碼
public class SynchronizedExample {
private final Object lock = new Object();
private int count = 0;
// 同步實(shí)例方法
public synchronized void increment() {
count++;
}
// 同步靜態(tài)方法
public static synchronized void staticMethod() {
// ...
}
// 同步代碼塊
public void doSomething() {
synchronized (lock) {
// 同步代碼
}
}
}
總結(jié)
synchronized 是 Java 中最基本的線程同步機(jī)制,通過(guò) JVM 的 Monitor 和精細(xì)的鎖升級(jí)策略(偏向鎖->輕量級(jí)鎖->重量級(jí)鎖)實(shí)現(xiàn)了在高性能無(wú)競(jìng)爭(zhēng)場(chǎng)景和高強(qiáng)度競(jìng)爭(zhēng)場(chǎng)景下的自適應(yīng)同步。其內(nèi)置的 wait/notify 機(jī)制為線程間協(xié)調(diào)提供了基礎(chǔ)。使用時(shí)需注意鎖的范圍、競(jìng)爭(zhēng)情況以及 wait/notify 的正確用法,以避免性能問(wèn)題和邏輯錯(cuò)誤。對(duì)于更復(fù)雜的場(chǎng)景,可以考慮 java.util.concurrent 包中的高級(jí)并發(fā)工具。
到此這篇關(guān)于java synchronized關(guān)鍵字用法和底層原理舉例詳解的文章就介紹到這了,更多相關(guān)java synchronized關(guān)鍵字用法內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
- 深入理解java中的synchronized關(guān)鍵字
- 詳解Java中synchronized關(guān)鍵字的死鎖和內(nèi)存占用問(wèn)題
- java多線程編程之使用Synchronized關(guān)鍵字同步類方法
- Java關(guān)鍵字volatile和synchronized作用和區(qū)別
- 舉例講解Java中synchronized關(guān)鍵字的用法
- 深入了解Java中Synchronized關(guān)鍵字的實(shí)現(xiàn)原理
- java synchronized關(guān)鍵字的用法
- Java中復(fù)雜的Synchronized關(guān)鍵字使用方法詳解
- Java多線程并發(fā)編程 Synchronized關(guān)鍵字
- Java 多線程synchronized關(guān)鍵字詳解(六)
相關(guān)文章
詳解Java Bellman-Ford算法原理及實(shí)現(xiàn)
Bellman-Ford算法與Dijkstra算法類似,都是以松弛操作作為基礎(chǔ),Bellman-Ford算法是對(duì)所有邊都進(jìn)行松弛操作,本文將詳解Bellman-Ford算法原理及實(shí)現(xiàn),感興趣的可以了解一下2022-07-07
java向數(shù)據(jù)庫(kù)插入數(shù)據(jù)顯示亂碼的幾種問(wèn)題解決
這篇文章主要給大家介紹了關(guān)于java向數(shù)據(jù)庫(kù)插入數(shù)據(jù)顯示亂碼問(wèn)題的解決方案,文章分別羅列了前臺(tái)亂碼的問(wèn)題、前臺(tái)先后臺(tái)插入數(shù)據(jù)后臺(tái)接收到的數(shù)據(jù)是亂碼以及后臺(tái)向數(shù)據(jù)庫(kù)插入數(shù)據(jù)是亂碼等幾種情況,需要的朋友可以參考下2021-11-11
Intellij IDEA如何去掉@Autowired 注入警告的方法
這篇文章主要介紹了Intellij IDEA如何去掉@Autowired 注入警告的方法,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2020-04-04
Java多線程、進(jìn)度條實(shí)現(xiàn)賽馬實(shí)驗(yàn)的示例代碼
這篇文章主要介紹了Java多線程、進(jìn)度條實(shí)現(xiàn)賽馬實(shí)驗(yàn)的示例代碼,代碼簡(jiǎn)單易懂,對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2020-11-11
IntelliJ IDEA 2020.3 重大特性(新功能一覽)
這篇文章主要介紹了IntelliJ IDEA 2020.3 重大特性(新功能一覽),本文通過(guò)圖文并茂的形式給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2020-12-12
【java 多線程】守護(hù)線程與非守護(hù)線程的詳解
這篇文章主要介紹了java守護(hù)線程與非守護(hù)線程,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2019-04-04
springboot集成Kaptcha實(shí)現(xiàn)圖片驗(yàn)證碼過(guò)程
這篇文章主要介紹了springboot集成Kaptcha實(shí)現(xiàn)圖片驗(yàn)證碼過(guò)程,具有很好的參考價(jià)值,希望對(duì)大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2025-06-06

