制:從對(duì)象頭到鎖升級(jí)的完整知識(shí)圖譜)
1. 面試題深度解析的價(jià)值與定位最近在幫團(tuán)隊(duì)做技術(shù)面試也和一些同行交流發(fā)現(xiàn)一個(gè)挺有意思的現(xiàn)象很多工作了三五年的Java開發(fā)者簡歷上“精通Java并發(fā)”寫得明明白白但一聊到synchronized這個(gè)最基礎(chǔ)、最核心的鎖機(jī)制能講清楚其底層實(shí)現(xiàn)、鎖升級(jí)過程以及各種應(yīng)用場景下細(xì)微差別的鳳毛麟角。大多數(shù)人還停留在“它是一個(gè)關(guān)鍵字用來加鎖保證線程安全”的層面。這其實(shí)挺危險(xiǎn)的因?yàn)閟ynchronized是理解Java內(nèi)存模型JMM、并發(fā)編程思想乃至JVM運(yùn)行時(shí)優(yōu)化的絕佳入口。一個(gè)看似簡單的面試題比如“說說synchronized的底層原理”背后串聯(lián)的是對(duì)象頭、Monitor、鎖膨脹、偏向鎖、輕量級(jí)鎖、重量級(jí)鎖、自旋鎖、鎖消除、鎖粗化等一系列硬核知識(shí)點(diǎn)。今天我就以一個(gè)面試官和一線開發(fā)者的雙重身份把圍繞synchronized可能問到的、最硬核的那些面試題掰開揉碎了講給你聽。目標(biāo)不是讓你背答案而是幫你建立起一個(gè)從Java代碼到JVM實(shí)現(xiàn)再到CPU指令的完整知識(shí)圖譜下次面試時(shí)能真正“言之有物”。2. synchronized的基石Java對(duì)象頭與Monitor機(jī)制要徹底搞懂synchronized絕對(duì)不能從關(guān)鍵字本身開始而要從它作用的對(duì)象——或者說任何Java對(duì)象——的內(nèi)存布局說起。這是所有后續(xù)問題的根基。2.1 對(duì)象內(nèi)存布局探秘一個(gè)普通的Java對(duì)象在堆內(nèi)存中除了我們熟悉的實(shí)例數(shù)據(jù)instance data和對(duì)齊填充padding之外最容易被忽略但至關(guān)重要的部分就是對(duì)象頭Object Header。對(duì)于HotSpot虛擬機(jī)對(duì)象頭主要包含兩部分Mark Word標(biāo)記字段這是核心中的核心存儲(chǔ)了對(duì)象自身的運(yùn)行時(shí)數(shù)據(jù)。它的長度在32位JVM中是32位64位JVM中是64位。你可別小看這幾十個(gè)比特它是個(gè)“變臉大師”會(huì)根據(jù)對(duì)象的狀態(tài)是否被鎖定、是否可偏向等復(fù)用這些位來存儲(chǔ)不同的信息。Klass Pointer類型指針指向?qū)ο笏鶎兕惖脑獢?shù)據(jù)Class對(duì)象的指針JVM通過它來確定這個(gè)對(duì)象是哪個(gè)類的實(shí)例。我們重點(diǎn)看Mark Word。在對(duì)象未被同步鎖定的普通狀態(tài)下它存儲(chǔ)的是對(duì)象的哈希碼identity hash code、GC分代年齡等信息。但是一旦這個(gè)對(duì)象被synchronized盯上它的Mark Word就立刻“變身”用來存儲(chǔ)指向重量級(jí)鎖Monitor的指針或者用來存儲(chǔ)輕量級(jí)鎖、偏向鎖的線程ID和鎖記錄指針。這里有個(gè)關(guān)鍵細(xì)節(jié)對(duì)象的哈希碼。如果一個(gè)對(duì)象已經(jīng)計(jì)算過hashCode()注意這里指的是System.identityHashCode(Object)返回的而非用戶重寫的hashCode這個(gè)哈希碼會(huì)存儲(chǔ)在Mark Word中。而偏向鎖和哈希碼是互斥的。因?yàn)槠蜴i需要占用Mark Word的空間來存儲(chǔ)線程ID如果已經(jīng)存了哈希碼就沒地方存線程ID了所以這個(gè)對(duì)象永遠(yuǎn)無法進(jìn)入偏向鎖狀態(tài)。這是一個(gè)常被忽略但非常重要的考點(diǎn)。2.2 Monitor重量級(jí)鎖的實(shí)體當(dāng)synchronized升級(jí)為重量級(jí)鎖時(shí)Mark Word里存儲(chǔ)的指針指向的就是一個(gè)ObjectMonitor對(duì)象這就是我們常說的管程Monitor。你可以把它想象成一個(gè)“房間”這個(gè)房間有唯一的入口并且房間里有一些特殊的設(shè)施。一個(gè)ObjectMonitor對(duì)象內(nèi)部有幾個(gè)關(guān)鍵字段_owner指向持有該Monitor的線程。如果為NULL表示鎖未被任何線程持有。_WaitSet一個(gè)等待隊(duì)列所有調(diào)用Object.wait()方法而進(jìn)入等待狀態(tài)的線程會(huì)被放入這個(gè)集合。_EntryList一個(gè)阻塞隊(duì)列所有嘗試獲取鎖但發(fā)現(xiàn)_owner不為空鎖已被占用的線程會(huì)被放入這個(gè)隊(duì)列等待鎖釋放后被喚醒競爭。synchronized修飾的代碼塊在編譯后會(huì)被翻譯成monitorenter和monitorexit或通過異常表確保執(zhí)行的monitorexit這一對(duì)字節(jié)碼指令。當(dāng)線程執(zhí)行到monitorenter時(shí)它會(huì)嘗試進(jìn)入這個(gè)“房間”Monitor。如果_owner為空它就把自己設(shè)為_owner然后執(zhí)行同步塊內(nèi)的代碼。如果_owner不為空它就得去_EntryList里排隊(duì)等著。執(zhí)行完代碼后monitorexit指令會(huì)釋放鎖將_owner置空并喚醒_EntryList或_WaitSet中的線程來競爭。注意很多人誤以為synchronized是“非公平鎖”。嚴(yán)格來說在重量級(jí)鎖狀態(tài)下當(dāng)鎖被釋放時(shí)_EntryList中的線程和新來的線程尚未進(jìn)入_EntryList會(huì)一起競爭這個(gè)競爭過程是“非公平”的新來的線程有可能“插隊(duì)”成功。但JVM內(nèi)部有一些優(yōu)化情況比較復(fù)雜面試時(shí)通常說它是“非公平鎖”是可以接受的。3. 鎖的升級(jí)與優(yōu)化從偏向鎖到重量級(jí)鎖如果每次加鎖都直接走重量級(jí)鎖的流程涉及操作系統(tǒng)內(nèi)核態(tài)的互斥量操作需要從用戶態(tài)切換到內(nèi)核態(tài)性能開銷大那Java并發(fā)性能就太糟糕了。所以JVM設(shè)計(jì)了一套精妙的鎖升級(jí)膨脹策略目的是在絕大多數(shù)無競爭或低競爭的場景下用更小的開銷來實(shí)現(xiàn)同步。3.1 鎖升級(jí)的全景圖鎖的狀態(tài)升級(jí)是不可逆的偏向鎖可以被撤銷回到無鎖但升級(jí)過程是單向的總體路徑是無鎖 - 偏向鎖 - 輕量級(jí)鎖 - 重量級(jí)鎖。這個(gè)升級(jí)過程是由競爭激烈程度驅(qū)動(dòng)的。1. 偏向鎖Biased Locking設(shè)計(jì)初衷研究發(fā)現(xiàn)在很多情況下鎖不僅不存在多線程競爭而且總是由同一個(gè)線程多次獲得。偏向鎖就是為了消除這個(gè)線程重入鎖的開銷。工作原理當(dāng)?shù)谝粋€(gè)線程訪問同步塊時(shí)JVM會(huì)使用CAS操作將線程ID記錄到對(duì)象頭的Mark Word中并將鎖標(biāo)志位設(shè)置為“01”偏向模式。之后這個(gè)線程再進(jìn)入和退出同步塊時(shí)不需要進(jìn)行任何同步操作如CAS、鎖釋放只需檢查Mark Word里存儲(chǔ)的線程ID是不是自己。如果是就直接通行開銷極小。撤銷Revoke一旦有另一個(gè)線程來嘗試競爭這個(gè)鎖偏向鎖模式就要被撤銷。撤銷過程需要等待全局安全點(diǎn)此時(shí)沒有正在執(zhí)行的字節(jié)碼然后暫停持有偏向鎖的線程檢查它是否還活著或已退出同步塊。如果已退出則將對(duì)象頭恢復(fù)為無鎖狀態(tài)或升級(jí)為輕量級(jí)鎖狀態(tài)允許新線程競爭如果仍持有則升級(jí)為輕量級(jí)鎖。注意事項(xiàng)偏向鎖有延遲激活機(jī)制默認(rèn)JVM啟動(dòng)后4秒并且由于撤銷開銷較大在明確存在高競爭的場景下如生產(chǎn)者-消費(fèi)者隊(duì)列可以通過JVM參數(shù)-XX:-UseBiasedLocking關(guān)閉偏向鎖直接使用輕量級(jí)鎖。2. 輕量級(jí)鎖Lightweight Locking適用場景當(dāng)偏向鎖被撤銷或者一開始就關(guān)閉了偏向鎖且存在多個(gè)線程交替執(zhí)行同步塊即競爭是“交替式”的而非“密集型”的此時(shí)升級(jí)為輕量級(jí)鎖。加鎖過程在當(dāng)前線程的棧幀中創(chuàng)建一個(gè)名為鎖記錄Lock Record的空間。將對(duì)象頭的Mark Word復(fù)制到鎖記錄中稱為Displaced Mark Word。然后線程嘗試使用CAS操作將對(duì)象頭的Mark Word替換為指向鎖記錄的指針并將鎖標(biāo)志位設(shè)置為“00”輕量級(jí)鎖狀態(tài)。如果CAS成功當(dāng)前線程獲得鎖。如果失敗說明至少存在兩條線程在競爭同一個(gè)鎖會(huì)觸發(fā)自旋鎖。自旋鎖Spin Lock競爭失敗的線程不會(huì)立即阻塞進(jìn)入重量級(jí)鎖的_EntryList而是采用循環(huán)的方式自旋去嘗試再次獲取鎖。這基于一個(gè)假設(shè)鎖的持有者很快就會(huì)釋放鎖。自旋避免了線程上下文切換的開銷用戶態(tài)操作但會(huì)空耗CPU。如果自旋超過一定次數(shù)JDK中自適應(yīng)自旋次數(shù)由前一次在同一個(gè)鎖上的自旋時(shí)間及鎖的持有者狀態(tài)決定仍未成功鎖就會(huì)膨脹為重量級(jí)鎖。解鎖過程使用CAS操作將Displaced Mark Word替換回對(duì)象頭。如果成功則解鎖完成。如果失敗說明鎖已經(jīng)膨脹為重量級(jí)鎖解鎖過程會(huì)喚醒阻塞的線程。3. 重量級(jí)鎖Heavyweight Locking如上文Monitor機(jī)制所述當(dāng)輕量級(jí)鎖競爭加劇自旋失敗鎖就會(huì)膨脹為重量級(jí)鎖。此時(shí)Mark Word中存儲(chǔ)的是指向Monitor對(duì)象的指針鎖標(biāo)志位變?yōu)椤?0”。未獲得鎖的線程會(huì)被阻塞進(jìn)入_EntryList隊(duì)列等待操作系統(tǒng)調(diào)度涉及用戶態(tài)到內(nèi)核態(tài)的切換開銷最大。3.2 鎖消除與鎖粗化除了鎖升級(jí)JVM還有兩項(xiàng)重要的編譯期優(yōu)化鎖消除Lock EliminationJIT編譯器在運(yùn)行時(shí)通過對(duì)“逃逸分析”技術(shù)的運(yùn)用如果發(fā)現(xiàn)一個(gè)鎖對(duì)象不可能被其他線程訪問到即該對(duì)象沒有“逃逸”出當(dāng)前線程那么即使代碼中顯式地加了synchronized編譯器也會(huì)安全地將這個(gè)鎖操作消除掉。最常見的例子就是在方法內(nèi)部創(chuàng)建的局部StringBuffer它的append方法是同步的但由于該對(duì)象不會(huì)逃逸鎖會(huì)被消除。鎖粗化Lock Coarsening如果一系列的連續(xù)操作都對(duì)同一個(gè)對(duì)象反復(fù)加鎖和解鎖甚至加鎖解鎖操作出現(xiàn)在循環(huán)體內(nèi)即使沒有線程競爭頻繁地進(jìn)行互斥同步操作也會(huì)導(dǎo)致不必要的性能損耗。JVM會(huì)探測到這種零碎的鎖操作并將多個(gè)連續(xù)的鎖范圍擴(kuò)大粗化為一個(gè)更大的鎖范圍從而減少鎖的獲取和釋放次數(shù)。例如在循環(huán)體內(nèi)調(diào)用一個(gè)同步方法JVM可能會(huì)將鎖提到循環(huán)體外。4. synchronized的三種應(yīng)用方式與字節(jié)碼差異synchronized有三種使用方式它們?cè)谡Z義上等價(jià)但在字節(jié)碼實(shí)現(xiàn)和鎖的粒度上有所不同。1. 同步實(shí)例方法public synchronized void instanceMethod() { // 同步代碼 }鎖對(duì)象當(dāng)前實(shí)例對(duì)象this。字節(jié)碼方法的訪問標(biāo)志ACC_SYNCHRONIZED被設(shè)置。當(dāng)線程調(diào)用該方法時(shí)會(huì)檢查此標(biāo)志。如果設(shè)置了執(zhí)行線程需要先成功持有管程即this對(duì)象的Monitor才能執(zhí)行方法體方法執(zhí)行完成后無論是正常返回還是異常拋出自動(dòng)釋放管程。monitorenter和monitorexit指令的調(diào)用是隱式的。2. 同步靜態(tài)方法public static synchronized void staticMethod() { // 同步代碼 }鎖對(duì)象當(dāng)前類的Class對(duì)象如MyClass.class。這是一個(gè)全局唯一的對(duì)象。字節(jié)碼同樣通過ACC_SYNCHRONIZED標(biāo)志實(shí)現(xiàn)但鎖對(duì)象是類的Class對(duì)象。3. 同步代碼塊public void method() { // 非同步代碼... synchronized (lockObject) { // 同步代碼 } // 非同步代碼... }鎖對(duì)象顯式指定的任意對(duì)象lockObject。這是最靈活的方式可以控制更細(xì)粒度的鎖。字節(jié)碼編譯后會(huì)在同步代碼塊前后明確生成monitorenter和monitorexit指令。monitorexit會(huì)有兩個(gè)一個(gè)用于正常退出一個(gè)放在異常表中用于異常退出確保鎖一定被釋放。重要對(duì)比與選擇粒度同步代碼塊的鎖粒度最細(xì)可以鎖定任意對(duì)象有助于減少鎖競爭提升并發(fā)度。同步方法鎖定了整個(gè)方法作用的對(duì)象this或Class對(duì)象粒度較粗。性能在早期版本中有人認(rèn)為同步代碼塊性能稍好因?yàn)橥絽^(qū)域更小。但在現(xiàn)代JVM強(qiáng)大的優(yōu)化下這種差異微乎其微。選擇哪種方式首要考慮的是邏輯正確性和鎖的粒度而非這點(diǎn)性能差異。一個(gè)經(jīng)典誤區(qū)synchronized(this)和同步實(shí)例方法鎖的是同一個(gè)對(duì)象當(dāng)前實(shí)例但synchronized(MyClass.class)和同步靜態(tài)方法鎖的也是同一個(gè)對(duì)象Class對(duì)象。不要混淆。5. 高頻硬核面試題場景剖析現(xiàn)在我們把這些知識(shí)應(yīng)用到具體的面試題場景中看看如何組織答案。場景一synchronized的底層實(shí)現(xiàn)原理初級(jí)回答它是Java關(guān)鍵字通過monitor實(shí)現(xiàn)編譯后會(huì)產(chǎn)生monitorenter和monitorexit指令。硬核回答應(yīng)從Java對(duì)象頭Mark Word講起說明它是鎖信息的載體。然后分情況討論無競爭時(shí)可能啟用偏向鎖Mark Word存儲(chǔ)線程ID。輕度競爭時(shí)升級(jí)為輕量級(jí)鎖通過線程棧中的鎖記錄和CAS操作實(shí)現(xiàn)。重度競爭時(shí)膨脹為重量級(jí)鎖Mark Word指向ObjectMonitor線程在操作系統(tǒng)的Monitor上排隊(duì)。最后要提到JIT編譯器的優(yōu)化如鎖消除和鎖粗化。這樣回答就展現(xiàn)了你對(duì)JVM運(yùn)行時(shí)行為的深入理解。場景二synchronized和ReentrantLock的區(qū)別這是一個(gè)經(jīng)典對(duì)比題不能只羅列API差異。特性synchronized (隱式鎖)ReentrantLock (顯式鎖)實(shí)現(xiàn)層面JVM層面實(shí)現(xiàn)原生語法支持JDK層面實(shí)現(xiàn)基于AQSAbstractQueuedSynchronizer鎖的獲取與釋放自動(dòng)獲取和釋放進(jìn)入同步塊獲取退出正常或異常釋放必須手動(dòng)lock()和unlock()通常放在try-finally塊中靈活性有限。不可中斷、不可設(shè)置超時(shí)、非公平默認(rèn)靈活??芍袛?lockInterruptibly())、可超時(shí)(tryLock(timeout))、可公平構(gòu)造函數(shù)指定條件隊(duì)列單一通過wait()/notify()/notifyAll()操作多個(gè)通過newCondition()創(chuàng)建多個(gè)Condition對(duì)象實(shí)現(xiàn)更精細(xì)的線程通信如生產(chǎn)者-消費(fèi)者模型性能早期版本性能較差但經(jīng)過大量優(yōu)化鎖升級(jí)后在低競爭場景下性能極佳在高競爭場景下由于其可配置的靈活性可能表現(xiàn)更穩(wěn)定調(diào)試獲取鎖的信息較少堆棧信息可能不夠清晰提供了一些監(jiān)控方法如getHoldCount(),getQueueLength()等核心要點(diǎn)synchronized是“傻瓜相機(jī)”簡單可靠JVM負(fù)責(zé)優(yōu)化。ReentrantLock是“單反相機(jī)”功能強(qiáng)大但需要手動(dòng)控制用不好容易出錯(cuò)如忘記解鎖導(dǎo)致死鎖。在不需要ReentrantLock高級(jí)特性的場景下優(yōu)先使用synchronized。場景三什么是鎖升級(jí)能詳細(xì)描述一下過程嗎這就是對(duì)本章節(jié)3.1內(nèi)容的綜合闡述。回答時(shí)要有條理目的為了在無競爭或低競爭時(shí)減少鎖開銷。步驟初始對(duì)象為無鎖狀態(tài)。第一個(gè)線程訪問JVM啟用偏向鎖將線程ID CAS寫入Mark Word。第二個(gè)線程來競爭撤銷偏向鎖。如果原線程已退出同步塊則變?yōu)闊o鎖新線程嘗試加輕量級(jí)鎖如果原線程仍持有則直接升級(jí)為輕量級(jí)鎖。輕量級(jí)鎖競爭線程通過CAS和自旋嘗試獲取鎖。若自旋失敗或自旋期間有第三個(gè)線程來競爭則膨脹為重量級(jí)鎖。補(bǔ)充提及鎖消除和鎖粗化作為編譯期優(yōu)化。場景四synchronized是公平鎖嗎直接回答不是默認(rèn)是非公平鎖。解釋在重量級(jí)鎖狀態(tài)下當(dāng)鎖釋放時(shí)正在_EntryList中等待的線程和新來的線程會(huì)一起競爭新來的線程有可能直接獲取到鎖這對(duì)已等待的線程是不公平的。ReentrantLock可以通過構(gòu)造函數(shù)選擇公平或非公平策略。場景五雙重檢查鎖定DCL單例模式中為什么要加volatile這是synchronized與volatile結(jié)合的經(jīng)典考題。public class Singleton { private static volatile Singleton instance; // 必須volatile private Singleton() {} public static Singleton getInstance() { if (instance null) { // 第一次檢查 synchronized (Singleton.class) { if (instance null) { // 第二次檢查 instance new Singleton(); // 問題所在 } } } return instance; } }問題根源instance new Singleton();這行代碼并非原子操作它大致分為三步分配內(nèi)存空間。初始化對(duì)象調(diào)用構(gòu)造函數(shù)等。將內(nèi)存地址賦值給instance引用。由于指令重排序編譯器和處理器優(yōu)化步驟2和步驟3的順序可能顛倒。如果線程A執(zhí)行到步驟3instance已不為null但步驟2還未完成時(shí)線程B執(zhí)行到第一次檢查if (instance null)會(huì)發(fā)現(xiàn)instance不為null于是直接返回一個(gè)尚未初始化完成的對(duì)象導(dǎo)致程序出錯(cuò)。volatile的作用volatile關(guān)鍵字在這里有兩個(gè)作用禁止指令重排序確保instance new Singleton();操作的步驟1、2、3按順序完成。保證可見性確保一旦instance被初始化完成其他線程能立即看到最新的值。因此DCL模式中synchronized保證了創(chuàng)建過程的原子性volatile保證了創(chuàng)建過程的有序性和結(jié)果的可見性二者缺一不可。6. 實(shí)戰(zhàn)中的避坑指南與性能調(diào)優(yōu)懂了原理還要會(huì)在實(shí)際項(xiàng)目中用好。這里分享幾個(gè)我踩過的坑和調(diào)優(yōu)經(jīng)驗(yàn)。避坑指南1鎖對(duì)象選擇不當(dāng)導(dǎo)致非預(yù)期同步// 反例1鎖住了可變對(duì)象 private final ListString lock new ArrayList(); public void method() { synchronized(lock) { lock.add(something); // 鎖對(duì)象本身被修改雖然引用未變但極不推薦容易引起混淆。 } } // 反例2鎖住了緩存對(duì)象如Integer, String常量池中的對(duì)象 private static final String LOCK LOCK; public void method() { synchronized(LOCK) { // 危險(xiǎn)LOCK在常量池中是唯一的可能被其他毫不相干的代碼段使用導(dǎo)致意外的鎖競爭和死鎖。 // ... } }最佳實(shí)踐專門聲明一個(gè)私有的、不可變的final、僅用于鎖定的對(duì)象。private final Object lock new Object(); // 專鎖專用 public void method() { synchronized(lock) { // ... } }避坑指南2鎖粒度太粗引發(fā)性能瓶頸一個(gè)類里所有同步方法都鎖this或者一個(gè)龐大的方法被synchronized修飾會(huì)導(dǎo)致線程長時(shí)間持有鎖其他線程嚴(yán)重阻塞。優(yōu)化使用同步代碼塊只鎖住真正需要保護(hù)的共享資源部分。或者根據(jù)不同的資源使用不同的鎖對(duì)象鎖分離例如讀寫鎖分離。性能調(diào)優(yōu)經(jīng)驗(yàn)監(jiān)控鎖競爭使用jstack、jconsole、VisualVM等工具查看線程狀態(tài)。大量線程處于BLOCKED狀態(tài)等待monitor entry是鎖競爭激烈的明顯信號(hào)??紤]關(guān)閉偏向鎖對(duì)于明確存在高并發(fā)競爭的服務(wù)如秒殺系統(tǒng)、消息隊(duì)列的Worker可以在JVM啟動(dòng)參數(shù)中加上-XX:-UseBiasedLocking。因?yàn)槠蜴i的撤銷在高競爭下反而會(huì)成為負(fù)擔(dān)直接使用輕量級(jí)鎖起步可能效率更高。謹(jǐn)慎使用synchronized修飾靜態(tài)方法這鎖的是Class對(duì)象全局唯一影響范圍廣。除非確實(shí)需要全局同步否則優(yōu)先考慮同步實(shí)例方法或代碼塊。理解自旋的代價(jià)輕量級(jí)鎖的自旋雖然避免了上下文切換但在單核CPU或競爭非常激烈的情況下會(huì)白白浪費(fèi)CPU周期。JVM的自適應(yīng)自旋Adaptive Spinning機(jī)制會(huì)動(dòng)態(tài)調(diào)整自旋次數(shù)通常不需要我們手動(dòng)干預(yù)。synchronized的深度遠(yuǎn)不止于此它還與wait()、notify()、notifyAll()共同構(gòu)成了Java原生的線程間通信機(jī)制與volatile共同構(gòu)建了Java內(nèi)存模型的happens-before規(guī)則。但掌握了對(duì)象頭、Monitor、鎖升級(jí)這三大核心支柱以及各種應(yīng)用場景下的權(quán)衡你已經(jīng)能應(yīng)對(duì)市面上99%關(guān)于它的深度拷問了。記住面試官問synchronized很多時(shí)候不是在問一個(gè)關(guān)鍵字而是在考察你對(duì)Java并發(fā)體系、JVM運(yùn)行機(jī)制的理解深度。把這一套東西內(nèi)化你的并發(fā)功底自然就上了一個(gè)臺(tái)階。