Java 中常見的鎖與最佳實(shí)踐
Java 中的鎖可以從多個(gè)維度來劃分,常見的鎖包括:
- 語法/實(shí)現(xiàn)層面:
synchronized關(guān)鍵字(內(nèi)置鎖)、Lock接口及其實(shí)現(xiàn)類(如ReentrantLock、ReentrantReadWriteLock、StampedLock)。 - 特性層面:可重入鎖、公平鎖/非公平鎖、讀寫鎖、樂觀鎖/悲觀鎖、自旋鎖、分段鎖等。
- 并發(fā)工具包中的輔助鎖:
Semaphore(信號量,可看作共享鎖)、CountDownLatch、CyclicBarrier等雖不是嚴(yán)格意義上的鎖,但也用于控制線程協(xié)作。
下面我們從最常用的幾個(gè)維度展開聊聊。
1. 從語法和 API 層面看
synchronized(內(nèi)置鎖)
- 使用方式:修飾實(shí)例方法、靜態(tài)方法或代碼塊。
- 原理:基于對象頭的 Mark Word 和 monitor 對象實(shí)現(xiàn)。JDK 1.6 以后引入了偏向鎖、輕量級鎖、重量級鎖的升級過程,以優(yōu)化性能。
- 特點(diǎn):使用簡單,自動釋放鎖(異?;蛘M顺鰰r(shí)),可重入。但無法中斷等待鎖的線程,也無法設(shè)置超時(shí)。
- 最佳實(shí)踐:適用于鎖的競爭不激烈、代碼簡單的情況。比如單例模式的同步方法、簡單的線程安全計(jì)數(shù)器。
Lock接口(顯式鎖)
最核心的實(shí)現(xiàn)是 ReentrantLock,它提供了比 synchronized 更靈活的鎖操作。
ReentrantLock- 示例:
ReentrantLock lock = new ReentrantLock(true); // 公平鎖
lock.lock();
try {
// 臨界區(qū)
} finally {
lock.unlock(); // 必須手動釋放
}- 可重入:同一個(gè)線程可以多次獲得同一把鎖。
- 公平/非公平:構(gòu)造函數(shù)可指定是否公平。公平鎖按線程等待順序獲取鎖,非公平鎖允許插隊(duì)(默認(rèn)非公平,吞吐量更高)。
- 可中斷:
lockInterruptibly()允許在等待鎖時(shí)響應(yīng)中斷。 - 超時(shí)獲取:
tryLock(long time, TimeUnit unit)可以指定等待時(shí)間。 - 配合 Condition:可實(shí)現(xiàn)多個(gè)等待/通知條件,比 wait/notify 更精細(xì)。
ReentrantReadWriteLock- 維護(hù)一對鎖:讀鎖(共享)和寫鎖(排他)。讀多寫少場景下可大幅提升并發(fā)度。
- 讀鎖可以被多個(gè)讀線程同時(shí)持有,但寫鎖獨(dú)占,且讀寫互斥。
- 可能出現(xiàn)“寫?zhàn)囸I”問題,但在非公平模式下,寫鎖優(yōu)先級通常較高。
StampedLock(JDK 8 引入)- 進(jìn)一步優(yōu)化讀多寫少場景,支持三種模式:寫鎖、讀鎖、樂觀讀。
- 樂觀讀:不加鎖,直接讀取數(shù)據(jù),之后通過 validate 檢查是否有寫操作發(fā)生。若無效再升級為讀鎖重讀。
- 特點(diǎn):不可重入,不支持條件變量,適合讀線程遠(yuǎn)多于寫線程的場景。
2. 從鎖的特性層面看
- 悲觀鎖 vs 樂觀鎖
- 悲觀鎖:假設(shè)并發(fā)沖突概率高,每次操作都加鎖(如 synchronized、ReentrantLock)。適合寫多場景。
- 樂觀鎖:假設(shè)沖突少,先操作,提交時(shí)用 CAS(Compare And Swap)檢測沖突,沖突則重試。如
java.util.concurrent.atomic包下的原子類。適合讀多寫少、沖突概率低的場景。
- 可重入鎖
- 同一線程可以重復(fù)獲取同一把鎖,避免死鎖。Java 中的 synchronized 和 ReentrantLock 都是可重入的。
- 公平鎖 vs 非公平鎖
- 公平鎖按線程請求鎖的順序獲取鎖,避免饑餓,但吞吐量較低;非公平鎖允許插隊(duì),吞吐量更高,但可能導(dǎo)致某些線程長時(shí)間得不到鎖。
- 自旋鎖
- 線程獲取鎖失敗時(shí)不立即掛起,而是循環(huán)嘗試(自旋),減少線程上下文切換的開銷。適合鎖持有時(shí)間短的場景。JDK 內(nèi)部大量使用了自旋鎖(如
ConcurrentLinkedQueue的入隊(duì)操作)。Java 中也提供了AbstractQueuedSynchronizer(AQS)對自旋和阻塞的支持。
- 線程獲取鎖失敗時(shí)不立即掛起,而是循環(huán)嘗試(自旋),減少線程上下文切換的開銷。適合鎖持有時(shí)間短的場景。JDK 內(nèi)部大量使用了自旋鎖(如
- 分段鎖
ConcurrentHashMap在 JDK 1.7 中的實(shí)現(xiàn),將數(shù)據(jù)分段,每段一把鎖,降低鎖粒度,提升并發(fā)度。JDK 1.8 改用 CAS + synchronized 對每個(gè)桶(數(shù)組元素)加鎖,相當(dāng)于進(jìn)一步細(xì)化。
3. 并發(fā)工具包中的 “鎖” 變體
Semaphore:計(jì)數(shù)信號量,可以控制同時(shí)訪問資源的線程數(shù)量(共享鎖)。比如限流場景。CountDownLatch:讓一個(gè)或多個(gè)線程等待,直到一組操作完成。CyclicBarrier:讓一組線程互相等待,到達(dá)某個(gè)屏障點(diǎn)后再同時(shí)執(zhí)行。
雖然這些不是傳統(tǒng)意義上的鎖,但它們也常被用來解決線程協(xié)作問題,面試時(shí)可以作為補(bǔ)充提及。
常見誤區(qū)與最佳實(shí)踐
- 誤區(qū)一:認(rèn)為 synchronized 性能總比 ReentrantLock 差。實(shí)際上 JDK 1.6 以后 synchronized 經(jīng)過優(yōu)化,性能與 ReentrantLock 差距不大,甚至在低競爭時(shí)更好。選擇時(shí)應(yīng)更關(guān)注功能需求。
- 誤區(qū)二:濫用讀寫鎖。如果讀操作很短,或者寫操作頻繁,讀寫鎖可能不如普通互斥鎖,因?yàn)榫S護(hù)讀鎖和寫鎖本身也有開銷。
- 誤區(qū)三:忘記在 finally 中釋放顯式鎖。這是新手最容易犯的錯(cuò)誤,可能導(dǎo)致死鎖。
- 最佳實(shí)踐:
- 優(yōu)先使用 synchronized,簡潔安全;需要高級功能(如可中斷、超時(shí)、公平性、多 Condition)時(shí)才選用 ReentrantLock。
- 讀多寫少且數(shù)據(jù)一致性要求不那么極致時(shí),可考慮 StampedLock 的樂觀讀模式,或使用 CopyOnWrite 容器。
- 對于簡單的原子操作,優(yōu)先使用 Atomic 系列類(樂觀鎖思想),避免鎖開銷。
- 了解 JVM 的鎖優(yōu)化(如偏向鎖、輕量級鎖),有助于調(diào)優(yōu)高并發(fā)應(yīng)用。
到此這篇關(guān)于Java 中常見的鎖有哪些?的文章就介紹到這了,更多相關(guān)java常見鎖內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Java網(wǎng)絡(luò)通信中URL與HTTP編程技術(shù)詳解
要想實(shí)現(xiàn)網(wǎng)絡(luò)編程,除了可以使用Socket之外,我們還可以利用URL編程或HTTP編程技術(shù),所以今天這篇文章,就給大家介紹一下URL編程和HTTP編程技術(shù),看看這兩種技術(shù)有什么特點(diǎn),文中有詳細(xì)的代碼講解,需要的朋友可以參考下2023-11-11
idea導(dǎo)入gradle項(xiàng)目時(shí)遇到的坑及解決
這篇文章主要介紹了idea導(dǎo)入gradle項(xiàng)目時(shí)遇到的坑及解決,具有很好的參考價(jià)值,希望對大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2025-04-04
詳解基于JWT的springboot權(quán)限驗(yàn)證技術(shù)實(shí)現(xiàn)
這篇文章主要介紹了詳解基于JWT的springboot權(quán)限驗(yàn)證技術(shù)實(shí)現(xiàn),文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2020-11-11
Java多線程批量數(shù)據(jù)導(dǎo)入的方法詳解
這篇文章主要介紹了Java多線程批量數(shù)據(jù)導(dǎo)入的方法,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,下面小編和大家來一起學(xué)習(xí)下吧2019-06-06
spring data jpa開啟批量插入、批量更新的問題解析
這篇文章主要介紹了spring data jpa開啟批量插入、批量更新問題,本文通過圖文實(shí)例相結(jié)合給大家介紹的非常詳細(xì),需要的朋友可以參考下2021-07-07
SpringBoot使用自定義注解實(shí)現(xiàn)權(quán)限攔截的示例
本篇文章主要介紹了SpringBoot使用自定義注解實(shí)現(xiàn)權(quán)限攔截的示例,具有一定的參考價(jià)值,有興趣的可以了解一下2017-09-09
RocketMQ的消費(fèi)者類型與最佳實(shí)踐詳解
這篇文章主要介紹了RocketMQ的消費(fèi)者類型與最佳實(shí)踐詳解,在?RocketMQ?5.0?中,更加強(qiáng)調(diào)了客戶端類型的概念,尤其是消費(fèi)者類型,為了滿足多樣的?RocketMQ?中一共有三種不同的消費(fèi)者類型,分別是?PushConsumer、SimpleConsumer?和?PullConsumer,需要的朋友可以參考下2023-10-10
SpringSecurity 默認(rèn)表單登錄頁展示流程源碼
本篇主要講解 SpringSecurity提供的默認(rèn)表單登錄頁 它是如何展示流程,本文圖文并茂給大家介紹的非常詳細(xì),具有一定的參考借鑒價(jià)值,需要的朋友參考下吧2020-01-01

