Java多線程中常見的鎖策略詳解
1. 悲觀鎖與樂觀鎖
悲觀鎖:為了保證原子性,因此把數(shù)據(jù)進(jìn)行上鎖,每一個(gè)不同的線程拿數(shù)據(jù)的時(shí)候都會(huì)參與鎖的競爭,其他線程想必須等待前者拿完數(shù)據(jù)解鎖后才能參與拿數(shù)據(jù)。
舉例,由于維修導(dǎo)致一層樓只剩下一間廁所。因此,線程1進(jìn)入廁所后,其他線程只能阻塞等待。

樂觀鎖:假設(shè)數(shù)據(jù)一般情況下不會(huì)產(chǎn)生并發(fā)沖突,所以在數(shù)據(jù)進(jìn)行提交更新的時(shí)候,才會(huì)正式對(duì)數(shù)據(jù)是否產(chǎn)生并發(fā)沖突進(jìn)行檢測,如果發(fā)現(xiàn)并發(fā)沖突了,則讓返回用戶錯(cuò)誤的信息,讓用戶決定如何去做。
還是上述上廁所例子,線程1 給其他線返回一個(gè)信息,其他線程可根據(jù)信息選擇換樓層上廁所亦或是等待。

以上的上廁所例子,在樓棟廁所不充足情況下。線程還是盲目選擇這棟樓的廁所(使用悲觀鎖)就會(huì)導(dǎo)致阻塞消耗系統(tǒng)資源。在樓棟廁所充足的情況下,線程選擇了這棟樓的廁所(使用樂觀鎖)這樣就能很好的利用系統(tǒng)資源。
synchronized 初始情況下使用的是樂觀鎖,當(dāng)發(fā)現(xiàn)鎖競爭激烈時(shí)候就會(huì)自動(dòng)轉(zhuǎn)換為悲觀鎖。就好比一個(gè)線程去某一棟樓上廁所,并不知道該樓棟是否廁所充足。充足就不阻塞等待,不充足就阻塞等待。
2. 讀寫鎖與互斥鎖
多線程中,線程作為讀取方不會(huì)產(chǎn)生線程安全問題,當(dāng)線程作為為寫入方和線程作為寫入方之間進(jìn)行交互和,線程作為寫入方和線程作為讀取方之間進(jìn)行交互,就會(huì)造成互斥。
線程對(duì)數(shù)據(jù)的訪問,主要存在三種情況:
- 線程只是對(duì)數(shù)據(jù)進(jìn)行讀操作,此時(shí)自然不會(huì)出現(xiàn)線程不安全問題。
- 多個(gè)線程對(duì)數(shù)據(jù)進(jìn)行寫操作,就會(huì)出現(xiàn)線程不安全問題。
- 一個(gè)線程對(duì)數(shù)據(jù)進(jìn)行讀操作,另個(gè)線程對(duì)數(shù)據(jù)進(jìn)行寫操作,也會(huì)出現(xiàn)線程不安全問題。
簡單的來說,線程的讀操作,就是線程對(duì)數(shù)據(jù)進(jìn)行訪問。線程的寫操作,就是線程對(duì)數(shù)據(jù)進(jìn)行修改。讀一下問題不大,但寫一下就難免會(huì)造成意外。因此,我們有了 讀寫鎖 這個(gè)概念。
讀寫鎖,就是把讀和寫這兩個(gè)操作分開來加鎖這樣就能避免互斥。Java 標(biāo)準(zhǔn)庫提供了一個(gè) ReentrantReadWriteLock 類,實(shí)現(xiàn)了讀寫鎖。
ReentrantReadWriteLock.ReadLock 類表示一個(gè)讀鎖.。這個(gè)對(duì)象提供了 lock / unlock 方法進(jìn)行加鎖解鎖。
ReentrantReadWriteLock.WriteLock 類表示一個(gè)寫鎖.。這個(gè)對(duì)象也提供了 lock / unlock 方法進(jìn)行加鎖解鎖。
- 讀加鎖與讀加鎖之間,不互斥
- 寫加鎖與寫加鎖之間,互斥
- 讀加鎖與寫加鎖之間,互斥
互斥,就會(huì)操作線程的掛起等待,一旦線程掛起等待了,就不知道什么時(shí)候能夠被喚醒了。因此,我們在編寫代碼的時(shí)候盡可能減少互斥。
讀寫鎖特別適用于“頻繁讀,不頻繁寫”的場景中。比如,學(xué)校的教務(wù)系統(tǒng):
假設(shè)計(jì)算機(jī)軟件專業(yè)的學(xué)生有 300 個(gè)同學(xué),這300個(gè)同學(xué)幾乎每天都要課程表為了防止課表更改,這樣的一個(gè)操作就是頻繁讀(訪問)。
有特殊情況,老師生病了或是怎樣,偶爾會(huì)調(diào)課到其他時(shí)間點(diǎn)。這樣的操作,就是不頻繁寫(修改)。
注意,synchronized 不是讀寫鎖。
3. 重量級(jí)鎖與輕量級(jí)鎖
在并發(fā)編程中,輕量級(jí)鎖和重量級(jí)鎖是兩種鎖的實(shí)現(xiàn)方法,主要用于解決多個(gè)線程同時(shí)訪問共享資源時(shí)的同步問題。
輕量級(jí)鎖通常用于鎖競爭不激烈的情況下,通過在線程內(nèi)部使用CAS操作來進(jìn)行加鎖和解鎖,這種方式不需要進(jìn)行線程的上下文切換,因此性能比重量級(jí)鎖更高。但是,如果鎖競爭激烈的話,輕量級(jí)鎖的性能優(yōu)勢就不明顯了。
重量級(jí)鎖通常用于鎖競爭激烈的情況下,通過將競爭鎖的線程掛起并切換到內(nèi)核態(tài)來進(jìn)行加鎖和解鎖。由于需要進(jìn)行線程的上下文切換,因此性能比輕量級(jí)鎖更低。但是,在鎖競爭激烈的情況下,重量級(jí)鎖的效果要比輕量級(jí)鎖好得多,因?yàn)樗梢杂行У乇苊怄i爭用問題,減少了線程的搶占和切換,從而提高了系統(tǒng)的效率和響應(yīng)速度。
synchronized 的輕量級(jí)鎖策略大概都是通過自旋鎖的方式實(shí)現(xiàn)的,重量級(jí)鎖則是掛起等待鎖。
4. 自旋鎖與掛起等待鎖
自旋鎖 VS 掛起等待鎖:
自旋鎖,當(dāng)線程之間進(jìn)行搶占鎖內(nèi)資源時(shí)候,線程1 已經(jīng)搶占到鎖,線程2 則會(huì)持續(xù)等待 線程1 鎖內(nèi)任務(wù)結(jié)束后再進(jìn)行搶占鎖資源,在這期間 線程2 持續(xù)處于阻塞等待狀態(tài)。
掛起等待鎖,當(dāng)掛起等待鎖遇到這種情況時(shí),發(fā)現(xiàn)有線程已經(jīng)搶占到鎖了,則會(huì)放棄阻塞等待。直到鎖開放了,則再參與搶占鎖。
因此,自旋鎖有以下優(yōu)缺點(diǎn):
- 優(yōu)點(diǎn):時(shí)刻占用系統(tǒng)資源,不涉及線程阻塞和調(diào)度,一旦鎖被釋放了,參與鎖的競爭。
- 缺點(diǎn):當(dāng)鎖內(nèi)任務(wù)比較復(fù)雜時(shí),鎖被其他線程占有時(shí)間過長,那么就會(huì)持續(xù)消耗系統(tǒng)資源。
- 掛起等待鎖則相反
4.1 自旋鎖
自旋鎖,按照正常的邏輯,當(dāng)線程搶占鎖時(shí)進(jìn)入阻塞狀態(tài),過不了多久鎖就被釋放了。因此,自旋鎖就沒必要放棄 CPU 了,一直占用著 CPU 的內(nèi)存空間。
自旋鎖偽代碼:
while(槍鎖lock == 失敗) {}如果獲取鎖失敗,立即再嘗試獲取鎖,無限循環(huán)下去直到獲取到鎖為止。第一次獲取鎖失敗,往后的獲取鎖操作會(huì)在極短的時(shí)間內(nèi)到來,一旦鎖被其他線程釋放,就能第一時(shí)間獲取到鎖。這就是輕量級(jí)鎖的體現(xiàn)(鎖的競爭還不太激烈,嘗試使用自旋方式加鎖)。
4.2 掛起等待鎖
當(dāng)線程獲取鎖失敗后,并不會(huì)進(jìn)行阻塞等待。而隨著系統(tǒng)的調(diào)度,不占用 CPU 。直到鎖開發(fā)后,再嘗試參與鎖競爭。這種情況就是掛起等待鎖,也是重量級(jí)鎖的體現(xiàn)(鎖的競爭太激烈了,線程跟隨系統(tǒng)的調(diào)度)。
舉例:
自旋鎖與掛起等待鎖的現(xiàn)實(shí)生活體現(xiàn):張三是一個(gè)普通的男生,如花是一個(gè)漂亮的女孩,在此張三作為線程,如花作為鎖。
張三開始追求如花,但是如花已經(jīng)有男朋友了。張三又是個(gè)死皮賴臉的人。每天堅(jiān)持給女孩發(fā)信息,期待著某一天如花分手,能得到如花。此時(shí)張三就處于自旋鎖的狀態(tài)。
隨著競爭的激烈,又有許多人想要追求如花。張三開始動(dòng)搖了,開始努力敲代碼、認(rèn)真學(xué)習(xí)不參與追如花的競爭了(隨著系統(tǒng)的調(diào)度做其他事去了)。如果某一天如花變?yōu)閱紊砹耍到y(tǒng)會(huì)通知張三如花單身了(鎖空閑了),張三就又開始參與競爭鎖。此時(shí)張三的狀態(tài)就是掛起等待鎖狀態(tài)。
5. 公平鎖與非公平鎖
公平鎖與非公平鎖講究四個(gè)字“公平競爭”,假設(shè)有三個(gè)線程搶占鎖資源,當(dāng)鎖被釋放后就會(huì)出現(xiàn)兩種情況:公平競爭鎖、非公平競爭鎖。
公平鎖:遵循先來后到的原則,線程1 進(jìn)入鎖,鎖釋放后。線程2 進(jìn)入鎖,鎖再釋放后。線程3 進(jìn)入鎖。整個(gè)過程是按照順序執(zhí)行的。
非公平鎖:由于線程之間搶占資源,導(dǎo)致鎖被無序的搶占。這樣 3 個(gè)線程都有機(jī)會(huì)優(yōu)先進(jìn)入鎖。整個(gè)過程會(huì)造成無序執(zhí)行。

通過上圖我們就能很好的理解,公平鎖與非公平鎖之間的差異。當(dāng)然,線程的調(diào)度是隨機(jī)的因此多個(gè)線程競爭鎖時(shí)可以隨意進(jìn)行搶占“手快有,手慢無”(非公平鎖)。要想實(shí)現(xiàn)公平鎖,我們可以使用一些特定的數(shù)據(jù)結(jié)構(gòu)來達(dá)到按順序使用鎖。
在實(shí)際開發(fā)中,公平鎖與非公平鎖沒有好壞之分,我們按照需求來進(jìn)行設(shè)置。注意,synchronized 屬于非公平鎖。
6. 可重入鎖與不可重入鎖
可重入鎖即允許一個(gè)線程多次獲取同一把鎖。
不可重入鎖是指一旦線程獲得了該鎖,此時(shí)再次請(qǐng)求獲取該鎖時(shí),系統(tǒng)會(huì)將該線程掛起,直到該鎖被釋放為止。因此,不可重入鎖不能再同一線程中重復(fù)獲取。
可重入鎖是指當(dāng)一個(gè)線程獲得了該鎖之后,在該鎖還未釋放之前,可以再次獲取該鎖。這種鎖可以防止死鎖的發(fā)生,因?yàn)樵讷@取之后可以在方法中重新獲取該鎖,從而避免死鎖的發(fā)生。
Java 中的 synchronized 關(guān)鍵字是一種可重入鎖,而 ReentrantLock 是 Java 中常用的可重入鎖類,synchronized 不需要手動(dòng)解鎖,而 ReentrantLock 需要手動(dòng)解鎖。
需要注意的是,可重入鎖雖然提高了代碼的靈活性和可維護(hù)性,但同時(shí)也可能會(huì)帶來出現(xiàn)深度嵌套鎖的風(fēng)險(xiǎn),引發(fā)死鎖或性能下降等問題。因此,在使用可重入鎖時(shí)需要仔細(xì)設(shè)計(jì)和管理。
談?wù)勀銓?duì)synchronized的演變過程的理解?
synchronized 既是悲觀鎖也是樂觀鎖,synchronized 即是輕量級(jí)鎖也是重量級(jí)鎖,synchronized 即是自旋鎖也是掛起等待鎖,synchronized 不是讀寫鎖,synchronized 是非公平鎖,synchronized是可重入鎖。
synchronized 的初始化的時(shí)候是一個(gè)樂觀鎖/輕量級(jí)鎖/自旋鎖,隨著synchronized的競爭激烈會(huì)升級(jí)為悲觀鎖/重量級(jí)鎖/掛起等待鎖,另外輕量級(jí)鎖是部分基于自旋鎖、重量級(jí)鎖是部分基于掛起等待鎖。
在鎖的策略中還會(huì)引申到“死鎖”的概念,在下篇博文中,我會(huì)介紹。大家也可以通過下方專欄中搜索多線程相關(guān)內(nèi)容。
到此這篇關(guān)于Java多線程中常見的鎖策略詳解的文章就介紹到這了,更多相關(guān)Java常見的鎖策略內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Java中實(shí)現(xiàn)Comparator接口和用法實(shí)例(簡明易懂)
這篇文章主要介紹了Java中實(shí)現(xiàn)Comparator接口和用法實(shí)例(簡明易懂),本文給出實(shí)現(xiàn)Comparator接口的實(shí)例和使用這個(gè)接口的代碼實(shí)例,需要的朋友可以參考下2015-05-05
JAVA多線程實(shí)現(xiàn)的四種方式及使用場景詳解
這篇文章主要介紹了JAVA多線程實(shí)現(xiàn)的四種方式及使用場景,并舉例說明了在管理系統(tǒng)中多線程的應(yīng)用場景,如數(shù)據(jù)導(dǎo)入導(dǎo)出、數(shù)據(jù)緩存更新、并發(fā)用戶操作處理、系統(tǒng)監(jiān)控和定時(shí)任務(wù)執(zhí)行,需要的朋友可以參考下2025-01-01
mybatis-generator 修改表結(jié)構(gòu)后實(shí)體不更新問題及解決方法
本文主要介紹了如何在MyBatis Generator中修改表結(jié)構(gòu)后更新實(shí)體類的問題,并提供了解決方案,本文給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友參考下吧2025-04-04
在springboot中如何集成clickhouse進(jìn)行讀寫操作
本文介紹了在Spring Boot中集成ClickHouse的步驟,包括引入依賴、配置數(shù)據(jù)源、編寫實(shí)體類和Mapper類進(jìn)行CRUD操作,特別提到批量插入時(shí)需要在SQL語句中添加`FORMAT`以避免錯(cuò)誤,在實(shí)際應(yīng)用中,與MySQL的操作類似,只需將ClickHouse當(dāng)作MySQL使用2024-11-11
IntelliJ?IDEA中如何使用Checkstyle對(duì)Java代碼規(guī)范檢查
這篇文章主要介紹了IntelliJ IDEA中如何使用Checkstyle對(duì)Java代碼規(guī)范檢查的相關(guān)資料,checkstyle是提高代碼質(zhì)量,檢查代碼規(guī)范的很好用的一款工具,文中通過代碼介紹的非常詳細(xì),需要的朋友可以參考下2026-03-03
Java的MyBatis框架項(xiàng)目搭建與hellow world示例
MyBatis框架為Java程序的數(shù)據(jù)庫操作帶來了很大的便利,這里我們就從最基礎(chǔ)的入手,來看一下Java的MyBatis框架項(xiàng)目搭建與hellow world示例,需要的朋友可以參考下2016-06-06
Java深拷貝與淺拷貝全維度解析(含面試?/?筆試?+?實(shí)戰(zhàn))
在Java中深拷貝與淺拷貝是對(duì)象復(fù)制的兩種方式,它們的主要區(qū)別在于對(duì)對(duì)象中引用類型字段的處理方式,這篇文章主要介紹了Java深拷貝與淺拷貝全維度解析的相關(guān)資料,文中通過代碼介紹的非常詳細(xì),需要的朋友可以參考下2026-01-01
springboot 項(xiàng)目容器啟動(dòng)后如何自動(dòng)執(zhí)行指定方法
這篇文章主要介紹了springboot 項(xiàng)目容器啟動(dòng)后如何自動(dòng)執(zhí)行指定方法,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2021-11-11

