Java中鎖的實(shí)現(xiàn)和內(nèi)存語(yǔ)義淺析
1. 概述
鎖是Java并發(fā)編程中最重要的同步機(jī)制。鎖除了讓臨界區(qū)互斥執(zhí)行外,還可以讓釋放鎖的線(xiàn)程獲取同一個(gè)鎖的線(xiàn)程發(fā)送消息。
鎖在實(shí)際使用時(shí)只是明白鎖限制了并發(fā)訪問(wèn), 但是鎖是如何實(shí)現(xiàn)并發(fā)訪問(wèn)的, 同學(xué)們可能不太清楚, 下面這篇文章就來(lái)揭開(kāi)鎖的神秘面紗.
2. 鎖的內(nèi)存語(yǔ)義
- 當(dāng)線(xiàn)程獲取鎖時(shí), JMM會(huì)把線(xiàn)程對(duì)應(yīng)的本地內(nèi)存置為無(wú)效. 從而使得被監(jiān)視器保護(hù)的臨界區(qū)的變量必須從主內(nèi)存中讀取.
- 當(dāng)線(xiàn)程釋放鎖時(shí), JMM會(huì)把該線(xiàn)程對(duì)應(yīng)的本地內(nèi)存中的共享變量刷新到主內(nèi)存中(并不是不釋放鎖就不刷新到主內(nèi)存, 只是釋放鎖時(shí)把未刷新到主內(nèi)存中的數(shù)據(jù)刷新到主內(nèi)存).
鎖的內(nèi)存語(yǔ)義與volatile的內(nèi)存語(yǔ)義
- 鎖獲取與volatile讀有相同的內(nèi)存語(yǔ)義.
- 鎖釋放與volatile寫(xiě)有相同的內(nèi)存語(yǔ)義.
內(nèi)存語(yǔ)義總結(jié)
- 線(xiàn)程A釋放一個(gè)鎖, 實(shí)質(zhì)上是線(xiàn)程A向接下來(lái)將要獲取這個(gè)鎖的某個(gè)線(xiàn)程發(fā)出了(線(xiàn)程A對(duì)共享變量所做修改的)消息.
- 線(xiàn)程B獲取一個(gè)鎖, 實(shí)質(zhì)上是線(xiàn)程B接收了之前某個(gè)線(xiàn)程發(fā)出的(在釋放這個(gè)鎖之前對(duì)共享變量所做修改的)消息.
- 線(xiàn)程A釋放鎖, 隨后線(xiàn)程B獲取這個(gè)鎖, 這個(gè)過(guò)程實(shí)質(zhì)上是線(xiàn)程A通過(guò)主內(nèi)存向線(xiàn)程B發(fā)送消息.
3. 鎖內(nèi)存語(yǔ)義的實(shí)現(xiàn)
下面以ReentrantLock為例, 獲取到鎖就是把state改為1(不考慮重入), 釋放鎖時(shí)改為0.
而加鎖的關(guān)鍵代碼就是
protected final boolean compareAndSetState(int expect, int update) { return unsafe.compareAndSwapInt(this, stateOffset, expect, update); }
該方法以原子操作的方式更新state變量, 本文把Java的compareAndSet()方法簡(jiǎn)稱(chēng)為CAS. JDK文檔對(duì)該方法的說(shuō)明如下: 如果當(dāng)前狀態(tài)值等于預(yù)期值, 則以原子方式將同步狀態(tài)設(shè)置為給定的更新值. 此操作具有volatile讀和寫(xiě)的內(nèi)存語(yǔ)義.
這里我們分別從編譯器和處理器的角度來(lái)分析: CAS如何同時(shí)具有volatile讀和volatile寫(xiě)的內(nèi)存語(yǔ)義.
我們知道, 編譯器不會(huì)對(duì)volatile讀與volatile讀后面的任意內(nèi)存操作重排序; 編譯器不會(huì)對(duì)volatile寫(xiě)與volatile寫(xiě)前面的任意內(nèi)存操作重排序. 組合這兩個(gè)條件, 意味著為了同時(shí)實(shí)現(xiàn)volatile讀和volatile寫(xiě)的內(nèi)存語(yǔ)義, 編譯器不能對(duì)CAS與CAS前面和后面的任意內(nèi)存操作重排序.
下面我們來(lái)分析在常見(jiàn)的intel X86處理器中, CAS是如何同時(shí)具有volatile讀和volatile寫(xiě)的內(nèi)存語(yǔ)義的.
下面是sun.misc.Unsafe類(lèi)的compareAndSwapInt()方法的源代碼.
public final native boolean compareAndSwapInt(Object var1, long var2, int var4, int var5);
可以看到, 這是一個(gè)本地方法調(diào)用. 這個(gè)本地方法在openjdk中依次調(diào)用的c++代碼為: unsafe.cpp, atomic.cpp 和 atomic_windows_x86.inline.hpp. 這個(gè)本地方法的最終實(shí)現(xiàn)在openjdk的如下位置: openjdk-7-fcs-src-b147-
27_jun_2011\openjdk\hotspot\src\os_cpu\windows_x86\vm\atomic_windows_x86.inline.hpp(對(duì)應(yīng)于
Windows操作系統(tǒng), X86處理器). 下面是對(duì)應(yīng)于intel X86處理器的源代碼的片段.
inline jint Atomic::cmpxchg (jint exchange_value, volatile jint* dest, jint compare_value) { // alternative for InterlockedCompareExchange int mp = os::is_MP(); __asm { mov edx, dest mov ecx, exchange_value mov eax, compare_value LOCK_IF_MP(mp) cmpxchg dword ptr [edx], ecx } }
如上面源代碼所示, 程序會(huì)根據(jù)當(dāng)前處理器的類(lèi)型來(lái)決定是否為cmpxchg指令添加lock前綴. 如果程序是在多處理器上運(yùn)行, 就為cmpxchg指令加上lock前綴(Lock Cmpxchg). 反之, 如果程序是在單處理器上運(yùn)行, 就省略lock前綴(單處理器自身會(huì)維護(hù)單處理器內(nèi)的順序一致性, 不需要lock前綴提供的內(nèi)存屏障效果).
intel的手冊(cè)對(duì)lock前綴的說(shuō)明如下.
- 確保對(duì)內(nèi)存的讀-改-寫(xiě)操作原子執(zhí)行. 在Pentium及Pentium之前的處理器中, 帶有l(wèi)ock前綴的指令在執(zhí)行期間會(huì)鎖住總線(xiàn), 使得其他處理器暫時(shí)無(wú)法通過(guò)總線(xiàn)訪問(wèn)內(nèi)存. 很顯然, 這會(huì)帶來(lái)昂貴的開(kāi)銷(xiāo). 從Pentium 4、Intel Xeon及P6處理器開(kāi)始, Intel使用緩存鎖定(Cache Locking)
來(lái)保證指令執(zhí)行的原子性. 緩存鎖定將大大降低lock前綴指令的執(zhí)行開(kāi)銷(xiāo). - 禁止該指令, 與之前和之后的讀和寫(xiě)指令重排序.
- 把寫(xiě)緩沖區(qū)中的所有數(shù)據(jù)刷新到內(nèi)存中.
上面的第2點(diǎn)和第3點(diǎn)所具有的內(nèi)存屏障效果, 足以同時(shí)實(shí)現(xiàn)volatile讀和volatile寫(xiě)的內(nèi)存語(yǔ)義.
經(jīng)過(guò)上面的分析, 現(xiàn)在我們終于能明白為什么JDK文檔說(shuō)CAS同時(shí)具有volatile讀和volatile寫(xiě)的內(nèi)存語(yǔ)義了.
從本文對(duì)ReentrantLock的分析可以看出, 鎖釋放-獲取的內(nèi)存語(yǔ)義的實(shí)現(xiàn)至少有下面兩種方式.
- 利用volatile變量的寫(xiě)-讀所具有的內(nèi)存語(yǔ)義.
- 利用CAS所附帶的volatile讀和volatile寫(xiě)的內(nèi)存語(yǔ)義.
4. 總結(jié)
對(duì)于鎖, 可以這么理解, N個(gè)線(xiàn)程去通過(guò)CAS去修改一個(gè)volatile變量, 但是由于CPU提供的機(jī)制, 只能有一個(gè)線(xiàn)程修改成功, 修改成功的線(xiàn)程獲得鎖, 其它線(xiàn)程以及后來(lái)的線(xiàn)程要么自旋一會(huì)兒, 要么直接掛起, 等待獲取鎖的線(xiàn)程釋放鎖時(shí)去喚醒. 就是這么個(gè)過(guò)程.
好了,以上就是這篇文章的全部?jī)?nèi)容了,希望本文的內(nèi)容對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,如果有疑問(wèn)大家可以留言交流,謝謝大家對(duì)腳本之家的支持。
- Java內(nèi)存泄漏問(wèn)題處理方法經(jīng)驗(yàn)總結(jié)
- java虛擬機(jī)深入學(xué)習(xí)之內(nèi)存管理機(jī)制
- Java內(nèi)存模型知識(shí)匯總
- 詳解Java內(nèi)存管理中的JVM垃圾回收
- 解決Java導(dǎo)入excel大量數(shù)據(jù)出現(xiàn)內(nèi)存溢出的問(wèn)題
- Java實(shí)現(xiàn)獲取cpu、內(nèi)存、硬盤(pán)、網(wǎng)絡(luò)等信息的方法示例
- Java四種遍歷Map的方法
- 小米推送Java代碼
- Java中的接口回調(diào)實(shí)例
- Java內(nèi)存區(qū)域和內(nèi)存模型講解
相關(guān)文章
SpringBoot使用JDBC獲取相關(guān)的數(shù)據(jù)方法
這篇文章主要介紹了SpringBoot使用JDBC獲取相關(guān)的數(shù)據(jù)方法,JDBC與數(shù)據(jù)庫(kù)建立連接、發(fā)送 操作數(shù)據(jù)庫(kù)的語(yǔ)句并處理結(jié)果,小編覺(jué)得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧2019-03-03java判斷class子類(lèi)或父類(lèi)的實(shí)例方法
在本篇文章里小編給大家整理的是關(guān)于java判斷class子類(lèi)或父類(lèi)的實(shí)例方法,需要的朋友們可以參考學(xué)習(xí)下。2020-02-02詳解如何使用XML配置來(lái)定義和管理Spring Bean
XML 配置文件是 Spring 中傳統(tǒng)的 Bean 配置方式,通過(guò)定義 XML 元素來(lái)描述 Bean 及其依賴(lài)關(guān)系,在 Spring 框架中,Bean 是由 Spring IoC(控制反轉(zhuǎn))容器管理的對(duì)象,本文將詳細(xì)介紹如何使用 XML 配置來(lái)定義和管理 Spring Bean,需要的朋友可以參考下2024-06-06Java編程學(xué)習(xí)的幾個(gè)典型實(shí)例詳解
這篇文章主要給大家介紹了Java編程學(xué)習(xí)的幾個(gè)典型實(shí)例,其中包括模擬酒店房間管理系統(tǒng)、螺旋矩陣 例或者百雞問(wèn)題的變形等經(jīng)典實(shí)例,具體來(lái)一起看詳細(xì)內(nèi)容吧,需要的朋友可以參考學(xué)習(xí)。2017-02-02基于resty?security的Api權(quán)限控制與事務(wù)支持
這篇文章主要為大家介紹了基于resty?security的Api權(quán)限控制與事務(wù)支持讓數(shù)據(jù)操作處于事務(wù)控制下,有需要的朋友可以借鑒參考下,希望能夠有所幫助2022-03-03springboot實(shí)現(xiàn)定時(shí)任務(wù)@Scheduled方式
這篇文章主要介紹了springboot實(shí)現(xiàn)定時(shí)任務(wù)@Scheduled方式,具有很好的參考價(jià)值,希望對(duì)大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2024-07-07Spring注解驅(qū)動(dòng)之BeanFactoryPostProcessor原理解析
這篇文章主要介紹了Spring注解驅(qū)動(dòng)之BeanFactoryPostProcessor原理,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2022-09-09