日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當(dāng)前位置: 首頁 > 编程语言 > java >内容正文

java

Java内存模型深度解析:锁--转

發(fā)布時間:2025/4/5 java 19 豆豆
生活随笔 收集整理的這篇文章主要介紹了 Java内存模型深度解析:锁--转 小編覺得挺不錯的,現(xiàn)在分享給大家,幫大家做個參考.

原文地址:http://www.codeceo.com/article/java-memory-5.html

鎖的釋放-獲取建立的happens before 關(guān)系

鎖是java并發(fā)編程中最重要的同步機制。鎖除了讓臨界區(qū)互斥執(zhí)行外,還可以讓釋放鎖的線程向獲取同一個鎖的線程發(fā)送消息。

下面是鎖釋放-獲取的示例代碼:

class MonitorExample {int a = 0; public synchronized void writer() { //1 a++; //2 } //3 public synchronized void reader() { //4 int i = a; //5 …… } //6 }

假設(shè)線程A執(zhí)行writer()方法,隨后線程B執(zhí)行reader()方法。根據(jù)happens before規(guī)則,這個過程包含的happens before 關(guān)系可以分為兩類:

  • 根據(jù)程序次序規(guī)則,1 happens before 2, 2 happens before 3; 4 happens before 5, 5 happens before 6。
  • 根據(jù)監(jiān)視器鎖規(guī)則,3 happens before 4。
  • 根據(jù)happens before 的傳遞性,2 happens before 5。
  • 上述happens before 關(guān)系的圖形化表現(xiàn)形式如下:

    在上圖中,每一個箭頭鏈接的兩個節(jié)點,代表了一個happens before 關(guān)系。黑色箭頭表示程序順序規(guī)則;橙色箭頭表示監(jiān)視器鎖規(guī)則;藍色箭頭表示組合這些規(guī)則后提供的happens before保證。

    上圖表示在線程A釋放了鎖之后,隨后線程B獲取同一個鎖。在上圖中,2 happens before 5。因此,線程A在釋放鎖之前所有可見的共享變量,在線程B獲取同一個鎖之后,將立刻變得對B線程可見。

    鎖釋放和獲取的內(nèi)存語義

    當(dāng)線程釋放鎖時,JMM會把該線程對應(yīng)的本地內(nèi)存中的共享變量刷新到主內(nèi)存中。以上面的MonitorExample程序為例,A線程釋放鎖后,共享數(shù)據(jù)的狀態(tài)示意圖如下:

    當(dāng)線程獲取鎖時,JMM會把該線程對應(yīng)的本地內(nèi)存置為無效。從而使得被監(jiān)視器保護的臨界區(qū)代碼必須要從主內(nèi)存中去讀取共享變量。下面是鎖獲取的狀態(tài)示意圖:

    對比鎖釋放-獲取的內(nèi)存語義與Volatile寫-讀的內(nèi)存語義,可以看出:鎖釋放與volatile寫有相同的內(nèi)存語義;鎖獲取與volatile讀有相同的內(nèi)存語義。

    下面對鎖釋放和鎖獲取的內(nèi)存語義做個總結(jié):

    • 線程A釋放一個鎖,實質(zhì)上是線程A向接下來將要獲取這個鎖的某個線程發(fā)出了(線程A對共享變量所做修改的)消息。
    • 線程B獲取一個鎖,實質(zhì)上是線程B接收了之前某個線程發(fā)出的(在釋放這個鎖之前對共享變量所做修改的)消息。
    • 線程A釋放鎖,隨后線程B獲取這個鎖,這個過程實質(zhì)上是線程A通過主內(nèi)存向線程B發(fā)送消息。

    鎖內(nèi)存語義的實現(xiàn)

    本文將借助ReentrantLock的源代碼,來分析鎖內(nèi)存語義的具體實現(xiàn)機制。

    請看下面的示例代碼:

    class ReentrantLockExample { int a = 0; ReentrantLock lock = new ReentrantLock(); public void writer() { lock.lock(); //獲取鎖 try { a++; } finally { lock.unlock(); //釋放鎖 } } public void reader () { lock.lock(); //獲取鎖 try { int i = a; …… } finally { lock.unlock(); //釋放鎖 } } }

    在ReentrantLock中,調(diào)用lock()方法獲取鎖;調(diào)用unlock()方法釋放鎖。

    ReentrantLock的實現(xiàn)依賴于java同步器框架AbstractQueuedSynchronizer(本文簡稱之為AQS)。AQS使用一個整型的volatile變量(命名為state)來維護同步狀態(tài),馬上我們會看到,這個volatile變量是ReentrantLock內(nèi)存語義實現(xiàn)的關(guān)鍵。 下面是ReentrantLock的類圖(僅畫出與本文相關(guān)的部分):

    ReentrantLock分為公平鎖和非公平鎖,我們首先分析公平鎖。

    使用公平鎖時,加鎖方法lock()的方法調(diào)用軌跡如下:

  • ReentrantLock : lock()
  • FairSync : lock()
  • AbstractQueuedSynchronizer : acquire(int arg)
  • ReentrantLock : tryAcquire(int acquires)
  • 在第4步真正開始加鎖,下面是該方法的源代碼:

    protected final boolean tryAcquire(int acquires) { final Thread current = Thread.currentThread(); int c = getState(); //獲取鎖的開始,首先讀volatile變量state if (c == 0) { if (isFirst(current) && compareAndSetState(0, acquires)) { setExclusiveOwnerThread(current); return true; } } else if (current == getExclusiveOwnerThread()) { int nextc = c + acquires; if (nextc < 0) throw new Error("Maximum lock count exceeded"); setState(nextc); return true; } return false; }

    從上面源代碼中我們可以看出,加鎖方法首先讀volatile變量state。

    在使用公平鎖時,解鎖方法unlock()的方法調(diào)用軌跡如下:

  • ReentrantLock : unlock()
  • AbstractQueuedSynchronizer : release(int arg)
  • Sync : tryRelease(int releases)
  • 在第3步真正開始釋放鎖,下面是該方法的源代碼:

    protected final boolean tryRelease(int releases) { int c = getState() - releases; if (Thread.currentThread() != getExclusiveOwnerThread()) throw new IllegalMonitorStateException(); boolean free = false; if (c == 0) { free = true; setExclusiveOwnerThread(null); } setState(c); //釋放鎖的最后,寫volatile變量state return free; }

    從上面的源代碼我們可以看出,在釋放鎖的最后寫volatile變量state。

    公平鎖在釋放鎖的最后寫volatile變量state;在獲取鎖時首先讀這個volatile變量。根據(jù)volatile的happens-before規(guī)則,釋放鎖的線程在寫volatile變量之前可見的共享變量,在獲取鎖的線程讀取同一個volatile變量后將立即變的對獲取鎖的線程可見。

    現(xiàn)在我們分析非公平鎖的內(nèi)存語義的實現(xiàn)。

    非公平鎖的釋放和公平鎖完全一樣,所以這里僅僅分析非公平鎖的獲取。

    使用公平鎖時,加鎖方法lock()的方法調(diào)用軌跡如下:

  • ReentrantLock : lock()
  • NonfairSync : lock()
  • AbstractQueuedSynchronizer : compareAndSetState(int expect, int update)
  • 在第3步真正開始加鎖,下面是該方法的源代碼:

    protected final boolean compareAndSetState(int expect, int update) { return unsafe.compareAndSwapInt(this, stateOffset, expect, update); }

    該方法以原子操作的方式更新state變量,本文把java的compareAndSet()方法調(diào)用簡稱為CAS。JDK文檔對該方法的說明如下:如果當(dāng)前狀態(tài)值等于預(yù)期值,則以原子方式將同步狀態(tài)設(shè)置為給定的更新值。此操作具有 volatile 讀和寫的內(nèi)存語義。

    這里我們分別從編譯器和處理器的角度來分析,CAS如何同時具有volatile讀和volatile寫的內(nèi)存語義。

    前文我們提到過,編譯器不會對volatile讀與volatile讀后面的任意內(nèi)存操作重排序;編譯器不會對volatile寫與volatile寫前面的任意內(nèi)存操作重排序。組合這兩個條件,意味著為了同時實現(xiàn)volatile讀和volatile寫的內(nèi)存語義,編譯器不能對CAS與CAS前面和后面的任意內(nèi)存操作重排序。

    下面我們來分析在常見的intel x86處理器中,CAS是如何同時具有volatile讀和volatile寫的內(nèi)存語義的。

    下面是sun.misc.Unsafe類的compareAndSwapInt()方法的源代碼:

    public final native boolean compareAndSwapInt(Object o, long offset, int expected, int x);

    可以看到這是個本地方法調(diào)用。這個本地方法在openjdk中依次調(diào)用的c++代碼為:unsafe.cpp,atomic.cpp和atomicwindowsx86.inline.hpp。這個本地方法的最終實現(xiàn)在openjdk的如下位置:openjdk-7-fcs-src-b147-27jun2011\openjdk\hotspot\src\oscpu\windowsx86\vm\ atomicwindowsx86.inline.hpp(對應(yīng)于windows操作系統(tǒng),X86處理器)。下面是對應(yīng)于intel x86處理器的源代碼的片段:

    // Adding a lock prefix to an instruction on MP machine // VC++ doesn't like the lock prefix to be on a single line // so we can't insert a label after the lock prefix. // By emitting a lock prefix, we can define a label after it. #define LOCK_IF_MP(mp) __asm cmp mp, 0 \__asm je L0 \ __asm _emit 0xF0 \ __asm L0: 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 } }

    如上面源代碼所示,程序會根據(jù)當(dāng)前處理器的類型來決定是否為cmpxchg指令添加lock前綴。如果程序是在多處理器上運行,就為cmpxchg指令加上lock前綴(lock cmpxchg)。反之,如果程序是在單處理器上運行,就省略lock前綴(單處理器自身會維護單處理器內(nèi)的順序一致性,不需要lock前綴提供的內(nèi)存屏障效果)。

    intel的手冊對lock前綴的說明如下:

  • 確保對內(nèi)存的讀-改-寫操作原子執(zhí)行。在Pentium及Pentium之前的處理器中,帶有l(wèi)ock前綴的指令在執(zhí)行期間會鎖住總線,使得其他處理器暫時無法通過總線訪問內(nèi)存。很顯然,這會帶來昂貴的開銷。從Pentium 4,Intel Xeon及P6處理器開始,intel在原有總線鎖的基礎(chǔ)上做了一個很有意義的優(yōu)化:如果要訪問的內(nèi)存區(qū)域(area of memory)在lock前綴指令執(zhí)行期間已經(jīng)在處理器內(nèi)部的緩存中被鎖定(即包含該內(nèi)存區(qū)域的緩存行當(dāng)前處于獨占或以修改狀態(tài)),并且該內(nèi)存區(qū)域被完全包含在單個緩存行(cache line)中,那么處理器將直接執(zhí)行該指令。由于在指令執(zhí)行期間該緩存行會一直被鎖定,其它處理器無法讀/寫該指令要訪問的內(nèi)存區(qū)域,因此能保證指令執(zhí)行的原子性。這個操作過程叫做緩存鎖定(cache locking),緩存鎖定將大大降低lock前綴指令的執(zhí)行開銷,但是當(dāng)多處理器之間的競爭程度很高或者指令訪問的內(nèi)存地址未對齊時,仍然會鎖住總線。
  • 禁止該指令與之前和之后的讀和寫指令重排序。
  • 把寫緩沖區(qū)中的所有數(shù)據(jù)刷新到內(nèi)存中。
  • 上面的第2點和第3點所具有的內(nèi)存屏障效果,足以同時實現(xiàn)volatile讀和volatile寫的內(nèi)存語義。

    經(jīng)過上面的這些分析,現(xiàn)在我們終于能明白為什么JDK文檔說CAS同時具有volatile讀和volatile寫的內(nèi)存語義了。

    現(xiàn)在對公平鎖和非公平鎖的內(nèi)存語義做個總結(jié):

    • 公平鎖和非公平鎖釋放時,最后都要寫一個volatile變量state。
    • 公平鎖獲取時,首先會去讀這個volatile變量。
    • 非公平鎖獲取時,首先會用CAS更新這個volatile變量,這個操作同時具有volatile讀和volatile寫的內(nèi)存語義。

    從本文對ReentrantLock的分析可以看出,鎖釋放-獲取的內(nèi)存語義的實現(xiàn)至少有下面兩種方式:

  • 利用volatile變量的寫-讀所具有的內(nèi)存語義。
  • 利用CAS所附帶的volatile讀和volatile寫的內(nèi)存語義。
  • concurrent包的實現(xiàn)

    由于java的CAS同時具有 volatile 讀和volatile寫的內(nèi)存語義,因此Java線程之間的通信現(xiàn)在有了下面四種方式:

  • A線程寫volatile變量,隨后B線程讀這個volatile變量。
  • A線程寫volatile變量,隨后B線程用CAS更新這個volatile變量。
  • A線程用CAS更新一個volatile變量,隨后B線程用CAS更新這個volatile變量。
  • A線程用CAS更新一個volatile變量,隨后B線程讀這個volatile變量。
  • Java的CAS會使用現(xiàn)代處理器上提供的高效機器級別原子指令,這些原子指令以原子方式對內(nèi)存執(zhí)行讀-改-寫操作,這是在多處理器中實現(xiàn)同步的關(guān)鍵(從本質(zhì)上來說,能夠支持原子性讀-改-寫指令的計算機器,是順序計算圖靈機的異步等價機器,因此任何現(xiàn)代的多處理器都會去支持某種能對內(nèi)存執(zhí)行原子性讀-改-寫操作的原子指令)。同時,volatile變量的讀/寫和CAS可以實現(xiàn)線程之間的通信。把這些特性整合在一起,就形成了整個concurrent包得以實現(xiàn)的基石。如果我們仔細分析concurrent包的源代碼實現(xiàn),會發(fā)現(xiàn)一個通用化的實現(xiàn)模式:

  • 首先,聲明共享變量為volatile;
  • 然后,使用CAS的原子條件更新來實現(xiàn)線程之間的同步;
  • 同時,配合以volatile的讀/寫和CAS所具有的volatile讀和寫的內(nèi)存語義來實現(xiàn)線程之間的通信。
  • AQS,非阻塞數(shù)據(jù)結(jié)構(gòu)和原子變量類(java.util.concurrent.atomic包中的類),這些concurrent包中的基礎(chǔ)類都是使用這種模式來實現(xiàn)的,而concurrent包中的高層類又是依賴于這些基礎(chǔ)類來實現(xiàn)的。從整體來看,concurrent包的實現(xiàn)示意圖如下:

    ?

    ?

    轉(zhuǎn)載于:https://www.cnblogs.com/davidwang456/p/6123129.html

    總結(jié)

    以上是生活随笔為你收集整理的Java内存模型深度解析:锁--转的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。

    如果覺得生活随笔網(wǎng)站內(nèi)容還不錯,歡迎將生活随笔推薦給好友。

    主站蜘蛛池模板: 久久精品免费网站 | 在线少妇 | 国产xxx视频| 中文字幕免费在线观看 | 三级欧美韩日大片在线看 | 国产又粗又猛又爽又黄的 | www.69视频| 中国xxxx性xxxx产国 | 日韩一区二区三 | 国产人妻aⅴ色偷 | 伊人久久成人网 | 日本免费黄色 | 日韩在线一卡 | bl动漫在线观看 | 久草久热 | 在线视频网 | 日本美女裸体视频 | 日韩欧美国产一区二区三区 | 岛国久久久 | www.狠狠干| 一级大片免费观看 | 美国少妇在线观看免费 | 蜜桃视频在线观看www | 亚洲在线影院 | 国产精品无码久久久久成人app | 久久激情综合网 | 欧美激情久久久久久久 | 51妺嘿嘿午夜福利 | 国产成人精品免费在线观看 | 91亚洲精品一区二区乱码 | a免费在线 | 永久av| 动漫3d精品一区二区三区乱码 | 亚洲精品wwww | 激情图片在线视频 | 4438全国成人免费 | 东方伊甸园av在线 | 波多野结衣精品视频 | 国产熟妇久久777777 | 精品一区二区三区视频在线观看 | 亚洲射图 | 99在线免费观看 | 欧美一区高清 | 日韩毛片在线观看 | 性chinese天美传媒麻 | 丁香婷婷网 | 妇女一级片 | 夜夜操夜夜爱 | 麻豆国产视频 | 久久我不卡 | 伊人久久天堂 | 性av网| 99精品在线观看 | 亚洲精品v | 强乱中文字幕av一区乱码 | 六月丁香婷婷网 | jzjzjz欧美丰满少妇 | 久久精品91 | 日韩成人黄色 | 91五月天| 国产一区二区三区日韩 | 亚洲免费在线观看av | 中文在线不卡视频 | 国产无套内射普通话对白 | 国产精品一区在线观看你懂的 | 美国av一区二区 | av网在线 | 久久精品一二区 | 河北彩花中文字幕 | 日本精品久久久久久久 | 人妻在线一区二区 | 亚洲国产精品综合久久久 | 爱福利视频一区二区 | 梦梦电影免费高清在线观看 | 男女午夜免费视频 | 精品人妻无码一区二区三区蜜桃一 | 国产精品久久久久久亚洲影视 | av片在线看| av免费网址在线观看 | 极品少妇xxxx精品少妇 | www.久久久久 | 一级黄色录象 | 亚洲喷水 | 欧美性猛交ⅹxx乱大交 | 叼嘿视频在线免费观看 | 国产又粗又黄视频 | 人妖ts福利视频一二三区 | 国产一级一片 | 久久精品亚洲a | 日本色一区 | 日本做爰高潮又黄又爽 | 国产综合无码一区二区色蜜蜜 | 精品国自产拍在线观看 | 久久先锋 | 疯狂做爰的爽文多肉小说王爷 | 99热在线播放 | 国精产品一品二品国精品69xx | 在线视频免费观看一区 | 久久免费国产精品 |