線程中并發的安全面試題
線程中并發的安全(重點)
1.synchronized關鍵字的底層原理
難5出現3
基本回顧定義

不加鎖會出現超賣的情況,但是會影響性能。
sunchronized是對象鎖采用互斥的方式讓同一時間最多只有一個線程能持有對象鎖,其他線程想獲取這個對象鎖時就會阻塞住

底層就是一個Monitor運用反編譯(javap)可以看見monitorenter上鎖moniterexit解鎖中間鎖住的部分就是鎖邏輯,會有兩個moniterexit為什么會有兩個呢 因為會隱式有一個tryfinally防止代碼拋出異常,如果有異常就走下面的釋放邏輯
Monitor監視器,由jvm提供,由c++實現
Monitor是三個屬性WaitSet 、EntryList、Owner。lock對象和Monitor關聯判斷Owner是不是空,如果是就獲得對象鎖,再來一個線程的話就會看Owner是不是空如果不是空就進入List中等待,等待的就處于阻塞狀態,等線程1執行完之后就爭搶Owner獲取鎖。當一個方法使用了wait方法之后就會處于waitSet中

回答
- 1.
Synchronized【對象鎖】采用互斥的方式讓同一時刻至多只有一個線程能持有【對象鎖】 - 2.它的底層由
monitor實現的,monitor是jvm級別的對象(C++實現)線程獲得鎖需要使用對象(鎖)關聯monitor - 3.在monitor內部有三個屬性,分別是owner、entrylist、waitset其中owner是關聯的獲得鎖的線程,并且只能關聯一個線程;entrylist關聯的是處于阻塞狀態的線程;waitset關聯的是處于Waiting狀態的線程
2.Synchronized底層實現進階
Monitor是重量級鎖,里面涉及了用戶態到內核態的切換,進程切換成本高,性能比較低。
jdk1.6之后引入了偏向鎖和輕量級鎖,他們的引入就是為了解決在沒有多線程競爭或基本沒有競爭的場景下使用傳統鎖所帶來的內存消耗問題和性能開銷的問題
重量級鎖每一個java對象都可以關聯一個Monitor對象,如果使用Synchronized東西上鎖(重量級鎖),該對象頭的MarkWord就被設置指向Monitor對象的指針
在HotSpot虛擬機中,對象在內存中可以分成3塊區域,對象頭、實例數據、對象填充

MarkWord重點 看原視頻吧 有點迷糊了

輕量級鎖
在很多情況下,java程序在運行的時候都是不存在競爭的,意思就是不同的線程交替執行同步代碼塊里面的代碼。這種情況下就可以使用輕量級鎖,輕量級鎖時可以重入的

偏向鎖
只有第一次使用cas講線程id設置到對象的Markword頭,之后發現線程id是自己就表示沒有競爭,不用重新cas以后只要不發生競爭這個對象就歸線程所以
java中的Synchronized有偏向鎖,輕量級鎖,重量級鎖三種形式。分別對應了鎖只被一個線程持有,不同線程交替持有鎖,多線程競爭鎖三種情況,一旦所發生競爭都會升級成重量級鎖

總結
- 基本作用與底層基礎
核心功能:作為對象鎖,通過互斥機制保證同一時間最多只有一個線程持有鎖,阻止多線程同時執行臨界區代碼,從而避免超賣等并發安全問題(但會帶來一定性能開銷)。 - 底層依賴:基于 JVM 提供的 Monitor(監視器,C++ 實現) 實現。線程獲取鎖的過程本質是競爭 Monitor 的所有權。
- Monitor 結構:包含 Owner(持有鎖的線程,唯一)、EntryList(等待獲取鎖的阻塞線程)、WaitSet(調用 wait() 后等待被喚醒的線程)。線程競爭鎖時,未獲取到鎖的線程進入 EntryList 阻塞;持有鎖的線程調用 wait() 會釋放鎖并進入 WaitSet。
- 鎖的升級機制(JDK 1.6 優化)
為解決傳統重量級鎖的性能問題(用戶態與內核態切換開銷大),引入了 鎖升級策略(不可逆),適應不同競爭強度場景:
偏向鎖:適用于單線程重復獲取鎖的場景。首次獲取鎖時,通過 CAS 將線程 ID 寫入對象頭的 MarkWord;后續該線程再次獲取鎖時,只需判斷線程 ID 匹配即可,無需 CAS 操作,開銷極小。
輕量級鎖:適用于多線程交替執行同步代碼的場景(輕度競爭)。線程獲取鎖時,會在棧幀中創建 Lock Record,并通過 CAS 將 MarkWord 指向該記錄;若 CAS 成功則獲取鎖,失敗則自旋重試(避免阻塞)。
重量級鎖:適用于多線程激烈競爭的場景。當自旋失敗(競爭加劇),輕量級鎖升級為重量級鎖,此時依賴 Monitor 和操作系統互斥量實現,未獲取鎖的線程進入 EntryList 阻塞(開銷較大)。
總結
synchronized 通過 對象鎖 + Monitor 機制 保證并發安全,同時通過 鎖升級(偏向鎖→輕量級鎖→重量級鎖) 優化性能,根據競爭強度動態切換鎖的實現方式,平衡了線程安全與執行效率。
面試回答
關于synchronized的底層原理,我可以從核心作用、實現基礎和優化機制三方面總結:
首先,它的核心是通過對象鎖的互斥性保證并發安全:同一時間最多只有一個線程能持有鎖,其他線程會阻塞,以此避免超賣等問題。
其次,底層依賴 JVM 的Monitor(監視器) 實現,這是一個 C++ 實現的 JVM 對象。同步代碼塊通過monitorenter和monitorexit指令控制鎖的獲取與釋放(兩個monitorexit確保異常時也能釋放);同步方法則通過方法元數據的ACC_SYNCHRONIZED標志實現。Monitor 內部有Owner(持有鎖的線程)、EntryList(阻塞等待鎖的線程)、WaitSet(調用wait()后等待喚醒的線程)三個關鍵部分,負責線程的競爭與狀態管理。
最后,JDK 1.6 引入了鎖升級機制優化性能(升級不可逆):單線程場景用偏向鎖,通過 MarkWord 記錄線程 ID,避免重復 CAS;多線程交替執行時升級為輕量級鎖,用棧幀中的 Lock Record 和 CAS 操作實現,減少阻塞;競爭激烈時升級為重量級鎖,依賴操作系統互斥量,此時線程會進入阻塞狀態,雖然開銷大但能保證線程絕對安全。
簡單說,synchronized通過 Monitor 實現基礎同步,再結合鎖升級策略,在安全與性能間做了很好的平衡。
3.jMM(java內存模型)
難3出現3
JMM(java Memory Model)內存模型,定義了共享內存中多線程程序讀寫的行為規范,通過這些規則來規范對內存讀寫操作從而保證指令的正確性。

工作內存中數據是對每個線程私有的,線程之間不可以互相訪問,多個線程之間要同步數據只能通過主內存來同步數據
面試回答:
- JMM(java Memory Model)內存模型,定義了共享內存中多線程程序讀寫的行為規范,通過這些規則來規范對內存讀寫操作從而保證指令的正確性。
- jMM把內存分成了2塊,一快十私有線程的工作區域(工作內存),一塊是所有線程的共享區域(主內存)
- 線程和線程之前互相隔離,線程和線程之間交互需要通過主內存來進行
4.知道CAS嗎
難3出現2
底層框架用的多 并發包很多包的類都用到了CAS 自旋鎖
CAS的全稱是Compare And Awap(比較再交換)體現樂觀鎖的思維在無鎖的情況下保證線程操作共享的原子性。

自旋鎖,沒有枷鎖,不會陷入阻塞狀態,效率高
如果競爭激烈重試的頻率高,效率就會收到影響就可以添加閾值,到多少之后還沒有自旋成功就放棄
CAS底層是直接調用Unasfe類來直接調用操作系統底層的CAS指令
樂觀鎖和悲觀鎖
CAS是基于樂觀鎖的思想:最樂觀的估計,不怕別的線程來修改共享變量,就算改了也沒關系,我自旋在重試
Synchronized是基于悲觀鎖的思想實現的,最悲觀的估計,放著其他線程來修改共享變量,我上了鎖你們都別想改,我改完了之后你們才有機會
面試回答
CAS知道嗎
全稱就是Compare And Swap(比較再交換)體現一種樂觀鎖的思想,在沒有鎖狀態下保證線程操作的原子性,再很多的框架中都使用了CAS,在操作共享變量的時候使用自旋鎖,效率會更高一些,CAS的底層是調用Unsafe方法,是其他的語言實現的
樂觀鎖和悲觀鎖的區別
CAS是基于樂觀鎖實現的,最樂觀的估計,不怕別的線程修改更新變量,就算改了也沒事,重試唄
Syschronized是基于悲觀鎖實現的,最悲觀的估計,防著其他線程來修改共享的變量,我上了鎖你們都別想修改,我改完了解開鎖你才有機會
5.談一談對Volatile的理解
難3出現3
關鍵字可以修飾共享的變量被volation修改之后就會有兩層含義
1.保證多線程之間的可見性
用volatile修飾的變量,讓一個線程對共享變量的修改可以對另一個線程可見,防止jit做優化(即使編譯器會對修飾的變量做優化)
2.禁止指令的重新排序
用Volation修飾共享變量會在讀,寫。共享變量的時候加入不同的屏障,防止其他線程越過屏障,從而達到阻止重排序的效果
6.什么是AQS
難3出現3
AbstactQueuedSynchronizer抽象的隊列同步器,構建鎖和其他同步組件的基礎
AQS與Synchronizer的區別
Synchronizer:關鍵字 c++實現 悲觀鎖,自動釋放鎖,重量級鎖性能差
AQS java實現 悲觀鎖,手動開啟和關閉 鎖競爭激烈的情況下,提供了多種解決方案
基本工作原理 會維護一個Volital修飾的變量State多線程共享有0無鎖1有鎖兩種情況,還會有個隊列判斷state為1就加入這個隊列,隊列是先進先出的隊列回維護對首和對尾兩個指針
AQS是公平鎖還是非公平鎖
都有吧,新線程與隊列中的線程共同搶占資源是非公平鎖。而對于隊列中的線程永遠都是讓head線程獲取鎖是公平鎖
面試回答

7.ReentrantLock實現原理
難4出現3
reentrantLock可重入鎖
相對于synchronized有好處:可中斷 可設置過期時間 可設置公平鎖 支持多個條件變量
主要利用了CAS+AQS來實現,支持公平鎖和非公平鎖,有兩個構造方法一個有參一個無參構造true是公平鎖,flase是非公平鎖,公平鎖吐出量低,會繼承AQS(AbstartQueuedSysnchronizer)

- 線程來搶鎖后使用cas的方式修改state狀態,修改狀態成功為1,則讓exclusiveOwnerThread屬性指向當前線程,獲取鎖成功
- 假如修改狀態失敗,則會進入雙向隊列中等待,head指向雙向隊列頭部,tail指向雙向隊列尾部
- 當exclusiveOwnerThread為null的時候,則會喚醒在雙向隊列中等待的線程
- 公平鎖則體現在按照先后順序獲取鎖,非公平體現在不在排隊的線程也可以搶鎖
8.synchronized和lock有什么區別
難4出現4
1.語法層面
synchronized是關鍵字,是c++實現的
Lock是接口,源碼是jdk實現的用java語言實現
synchronized退出代碼塊會同步釋放鎖,使用Lock必須手動釋放調用unLock來釋放鎖
2.功能層面
都屬于悲觀鎖,都具有互斥同步所重入的功能
Lock提供了許多Syschronized沒有的功能,公平鎖,可打斷,可超時,多條件變量 還有不同場景的實現,ReentrantLock,ReentrantReadWriteLock(讀寫鎖)
3.性能層面
在沒有競爭的時候Syschronize很多的優化比如偏向鎖,輕量級鎖,性能還行。
在競爭激烈的情況下Lock的實現通常會提供更好的性能
9.死鎖產生的條件
難3出現3
死鎖一個線程需要獲得多個鎖,就容易發生死鎖
- 互斥條件 同一時間只有一個線程可以獲取該資源
- 持有并等待條件 線程已經獲取了一個資源,同時有在等待其他資源
- 不可剝奪條件 線程獲取的資源在完畢之前不能被其他資源剝奪
- 循環等待條件 A等待b的資源 b等待1的資源
破壞其中的一個資源
死鎖的問題怎么診斷 jps 和 jstack
jps:輸出jvm中運行的進程程序的進程狀態信息
jstack:查看java進程中線程的堆棧信息
可視化工具
jconsole:java安裝bin目錄下直接啟動jconsole.exe
10,談一下ConcurrentHashMap
難3出現4
線程安全的高效的map集合
數據結構采用
- jdk1.7底層采用分段的數組+鏈表實現
- jdk1.8采用和hashmap一樣的數組+鏈表+紅黑樹
jdk1.7
jdk1.8

面試回答
1.底層數據結構:
JDK1.7底層采用分段的數組+鏈表實現
JDK1.8 采用的數據結構跟HashMap1.8的結構一樣,數組+鏈表/紅黑二叉樹
2.加鎖的方式
JDK1.7采用Seqment分段鎖,底層使用的是ReentrantLock
JDK1.8采用KAS添加新節點,采用synchronized鎖定鏈表或紅黑二叉樹的首節點,相對Segment分段鎖粒度更細,性能更好
11.導致并發程序出現問題的根本原因
java并發編程的三大特性
- 原子性 一個線程在cpu中操作是不可以暫停的要嘛全部完成要不不完成
- 可見性 一個線程對共享變量修改后另一個線程可見 加volation
- 有序性 指令重排 加volation
b站黑馬程序員面試筆記

浙公網安備 33010602011771號