最新国产好看的视频,伊人天堂AV在线,国产Aaaaaa视频,蜜臀视频在线观看一区,人妻av色图,密臀久久久精品影片,青青视频免费观看毛片,久草在线观看视,国产三级精品色情在线

Java實現(xiàn)synchronized鎖同步機制

 更新時間:2021年11月04日 11:11:55   作者:看山  
synchronized是java內(nèi)置的同步鎖實現(xiàn),本文就詳細的介紹一下Java實現(xiàn)synchronized鎖同步機制,具有一定的參考價值,感興趣的可以了解一下

synchronized 是 java 內(nèi)置的同步鎖實現(xiàn),一個關鍵字實現(xiàn)對共享資源的鎖定。synchronized 有 3 種使用場景,場景不同,加鎖對象也不同:

  • 普通方法:鎖對象是當前實例對象
  • 靜態(tài)方法:鎖對象是類的 Class 對象
  • 方法塊:鎖對象是 synchronized 括號中的對象

synchronized 實現(xiàn)原理

synchronized 是通過進入和退出 Monitor 對象實現(xiàn)鎖機制,代碼塊通過一對 monitorenter/monitorexit 指令實現(xiàn)。在編譯后,monitorenter 指令插入到同步代碼塊的開始位置,monitorexit 指令插入到方法結(jié)束和異常處,JVM 要保證 monitorenter 和 monitorexit 成對出現(xiàn)。任何對象都有一個 Monitor 與之關聯(lián),當且僅當一個 Monitor 被持有后,它將處于鎖狀態(tài)。

在執(zhí)行 monitorenter 時,首先嘗試獲取對象的鎖,如果對象沒有被鎖定或者當前線程持有鎖,鎖的計數(shù)器加 1;相應的,在執(zhí)行 monitorexit 指令時,將鎖的計數(shù)器減 1。當計數(shù)器減到 0 時,鎖釋放。如果在 monitorenter 獲取鎖失敗,當前線程會被阻塞,直到對象鎖被釋放。

在 JDK6 之前,Monitor 的實現(xiàn)是依靠操作系統(tǒng)內(nèi)部的互斥鎖實現(xiàn)(一般使用的是 Mutex Lock 實現(xiàn)),線程阻塞會進行用戶態(tài)和內(nèi)核態(tài)的切換,所以同步操作是一個無差別的重量級鎖。

后來,JDK 對 synchronized 進行升級,為了避免線程阻塞時在用戶態(tài)與內(nèi)核態(tài)之間切換線程,會在操作系統(tǒng)阻塞線程前,加入自旋操作。然后還實現(xiàn) 3 種不同的 Monitor:偏向鎖(Biased Locking)、輕量級鎖(Lightweight Locking)、重量級鎖。在 JDK6 之后,synchronized 的性能得到很大的提升,相比于 ReentrantLock 而言,性能并不差,只不過 ReentrantLock 使用起來更加靈活。

適應性自旋(Adaptive Spinning)

synchronized 對性能影響最大的是阻塞的實現(xiàn),掛起線程和恢復線程都需要操作系統(tǒng)幫助完成,需要從用戶態(tài)轉(zhuǎn)到內(nèi)核態(tài),狀態(tài)轉(zhuǎn)換需要耗費很多 CPU 時間。

在我們大多數(shù)的應用中,共享數(shù)據(jù)的鎖定狀態(tài)只會持續(xù)很短的一段時間,為了這段時間掛起和回復線程消耗的時間不值得。而且,現(xiàn)在大多數(shù)的處理器都是多核處理器,如果讓后一個線程再等一會,不釋放 CPU,等前一個釋放鎖,后一個線程立馬獲取鎖執(zhí)行任務就行。這就是所謂的自旋,讓線程執(zhí)行一個忙循環(huán),自己在原地轉(zhuǎn)一會,每轉(zhuǎn)一圈看看鎖釋放沒有,釋放了直接獲取鎖,沒有釋放就再轉(zhuǎn)一圈。

自旋鎖是在 JDK 1.4.2 引入(使用-XX:+UseSpinning參數(shù)打開),JDK 1.6 默認打開。自旋鎖不能代替阻塞,因為自旋等待雖然避免了線程切換的開銷,但是它要占用 CPU 時間,如果鎖占用時間短,自旋等待效果挺好,反之,則是性能浪費。所以在 JDK 1.6 中引入了自適應自旋鎖:如果同一個鎖對象,自旋等待剛成功,且持有鎖的線程正在運行,那本次自旋很有可能成功,會允許自旋等待持續(xù)時間長一些。反之,如果對于某個鎖,自旋很少成功,那之后很有可能直接省略自旋過程,避免浪費 CPU 資源。

鎖升級

Java 對象頭

synchronized 用的鎖存在于 Java 對象頭里,對象頭里的 Mark Word 里存儲的數(shù)據(jù)會隨標志位的變化而變化,變化如下:

Java 對象頭 Mark Word

偏向鎖(Biased Locking)

大多數(shù)情況下,鎖不僅不存在多線程競爭,而且總是由同一線程多次獲得,為了讓線程獲得鎖的代價更低,引入偏向鎖。
當一個線程訪問同步塊并獲取鎖時,會在對象頭和棧幀中的鎖記錄里存儲鎖偏向的線程 ID,以后該線程在進入和退出同步塊時不需要進行 CAS 操作來加鎖和解鎖,只需簡單地測試一下對象頭的 Mark Word 里是否存儲著指向當前線程的偏向鎖。引入偏向鎖是為了在無多線程競爭的情況下盡量減少不必要的輕量級鎖執(zhí)行路徑,因為輕量級鎖的獲取及釋放依賴多次 CAS 原子指令,而偏向鎖只需要在置換 ThreadID 的時候依賴一次 CAS 原子指令(由于一旦出現(xiàn)多線程競爭的情況就必須撤銷偏向鎖,所以偏向鎖的撤銷操作的性能損耗必須小于節(jié)省下來的 CAS 原子指令的性能消耗)。

偏向鎖獲取

  • 當鎖對象第一次被線程獲取時,對象頭的標志位設為 01,偏向模式設為 1,表示進入偏向模式。
  • 測試線程 ID 是否指向當前線程,如果是,執(zhí)行同步代碼塊,如果否,進入 3
  • 使用 CAS 操作把獲得到的這個鎖的線程 ID 記錄在對象的 Mark Word 中。如果成功,執(zhí)行同步代碼塊,如果失敗,說明存在過其他線程持有鎖對象的偏向鎖,開始嘗試當前線程獲取偏向鎖
  • 當?shù)竭_全局安全點時(沒有字節(jié)碼正在執(zhí)行),會暫停擁有偏向鎖的線程,檢查線程狀態(tài)。如果線程已經(jīng)結(jié)束,則將對象頭設置成無鎖狀態(tài)(標志位為“01”),然后重新偏向新的線程;如果線程仍然活著,撤銷偏向鎖后升級到輕量級鎖狀態(tài)(標志位為“00”),此時輕量級鎖由原持有偏向鎖的線程持有,繼續(xù)執(zhí)行其同步代碼,而正在競爭的線程會進入自旋等待獲得該輕量級鎖。

偏向鎖釋放

偏向鎖的釋放采用的是惰性釋放機制:只有等到競爭出現(xiàn),才釋放偏向鎖。釋放過程就是上面說的第 4 步,這里不再贅述。

關閉偏向鎖

偏斜鎖并不適合所有應用場景,撤銷操作(revoke)是比較重的行為,只有當存在較多不會真正競爭的同步塊時,才能體現(xiàn)出明顯改善。實踐中對于偏斜鎖的一直是有爭議的,有人甚至認為,當你需要大量使用并發(fā)類庫時,往往意味著你不需要偏斜鎖。

所以如果你確定應用程序里的鎖通常情況下處于競爭狀態(tài),可以通過 JVM 參數(shù)關閉偏向鎖:-XX:-UseBiasedLocking=false,那么程序默認會進入輕量級鎖狀態(tài)。

輕量級鎖(Lightweight Locking)

輕量級鎖不是用來代替重量級鎖的,它的初衷是在沒有多線程競爭的前提下,減少傳統(tǒng)的重量級鎖使用操作系統(tǒng)互斥量產(chǎn)生的性能損耗。

輕量級鎖獲取

如果同步對象鎖狀態(tài)為無鎖狀態(tài)(鎖標志位為“01”狀態(tài),是否為偏向鎖為“0”),虛擬機首先將在當前線程的棧幀中建立一個名為鎖記錄(Lock Record)的空間,用于存儲鎖對象目前的 Mark Word 的拷貝,官方稱之為 Displaced Mark Word。這時候線程堆棧與對象頭的狀態(tài)如下圖所示:

拷貝對象頭中的 Mark Word 復制到鎖記錄(Lock Record)中。

拷貝成功后,虛擬機將使用 CAS 操作嘗試將對象的 Mark Word 更新為指向 Lock Record 的指針,并將 Lock record 里的 owner 指針指向 object mark word。

如果成功,當前線程持有該對象鎖,將對象頭的 Mark Word 鎖標志位設置為“00”,表示對象處于輕量級鎖定狀態(tài),執(zhí)行同步代碼塊。這時候線程堆棧與對象頭的狀態(tài)如下圖所示:

如果更新失敗,檢查對象頭的 Mark Word 是否指向當前線程的棧幀,如果是,說明當前線程擁有鎖,直接執(zhí)行同步代碼塊。

如果否,說明多個線程競爭鎖,如果當前只有一個等待線程,通過自旋嘗試獲取鎖。當自旋超過一定次數(shù),或又來一個線程競爭鎖,輕量級鎖膨脹為重量級鎖。重量級鎖使除了擁有鎖的線程以外的線程都阻塞,防止 CPU 空轉(zhuǎn),鎖標志的狀態(tài)值變?yōu)椤?0”,Mark Word 中存儲的就是指向重量級鎖(互斥量)的指針,后面等待鎖的線程也要進入阻塞狀態(tài)。

輕量級鎖解鎖

  • 輕量級鎖解鎖的時機是,當前線程同步塊執(zhí)行完畢。
  • 通過 CAS 操作嘗試把線程中復制的 Displaced Mark Word 對象替換當前的 Mark Word。
  • 如果成功,整個同步過程完成
  • 如果失敗,說明存在競爭,且鎖膨脹為重量級鎖。釋放鎖的同時,會喚醒被掛起的線程。

重量級鎖

輕量級鎖適應的場景是線程近乎交替執(zhí)行同步塊的情況,如果存在同一時間訪問相同鎖對象時(第一個線程持有鎖,第二個線程自旋超過一定次數(shù)),輕量級鎖會膨脹為重量級鎖,Mark Word 的鎖標記位更新為 10,Mark Word 指向互斥量(重量級鎖)。

重量級鎖是通過對象內(nèi)部的一個叫做監(jiān)視器鎖(monitor)來實現(xiàn)的,監(jiān)視器鎖本質(zhì)又是依賴于底層的操作系統(tǒng)的 Mutex Lock(互斥鎖)。操作系統(tǒng)實現(xiàn)線程之間的切換需要從用戶態(tài)轉(zhuǎn)換到核心態(tài),這個成本非常高,狀態(tài)之間的轉(zhuǎn)換需要相對比較長的時間,這就是為什么 JDK 1.6 之前,synchronized 重量級鎖效率低的原因。

下圖是偏向鎖、輕量級鎖、重量級鎖之間轉(zhuǎn)換對象頭 Mark Word 數(shù)據(jù)轉(zhuǎn)變:

偏向鎖、輕量級鎖、重量級鎖之間轉(zhuǎn)換

網(wǎng)上有一個比較全的鎖升級過程:

鎖升級過程

鎖消除(Lock Elimination)

鎖消除說的是虛擬機即時編譯器在運行過程中,對于一些同步代碼,如果檢測到不可能存在共享數(shù)據(jù)競爭情況,就會刪除鎖。也就是說,即時編譯器根據(jù)情況刪除不必要的加鎖操作。
鎖消除的依據(jù)是逃逸分析。簡單地說,逃逸分析就是分析對象的動態(tài)作用域。分三種情況:

  • 不逃逸:對象的作用域只在本線程本方法
  • 方法逃逸:對象在方法內(nèi)定義后,被外部方法所引用
  • 線程逃逸:對象在方法內(nèi)定義后,被外部線程所引用

即時編譯器會針對對象的不同情況進行優(yōu)化處理:

  • 對象棧上分配(Stack Allocations,HotSpot 不支持):直接在棧上創(chuàng)建對象。
  • 標量替換(Scalar Replacement):將對象拆散,直接創(chuàng)建被方法使用的成員變量。前提是對象不會逃逸出方法范圍。
  • 同步消除(Synchronization Elimination):就是鎖消除,前提是對象不會逃逸出線程。

對于鎖消除來說,就是逃逸分析中,那些不會逃出線程的加鎖對象,就可以直接刪除同步鎖。

通過代碼看一個例子:

public void elimination1() {
    final Object lock = new Object();
    synchronized (lock) {
        System.out.println("lock 對象沒有只會作用域本線程,所以會鎖消除。");
    }
}

public String elimination2() {
    final StringBuffer sb = new StringBuffer();
    sb.append("Hello, ").append("World!");
    return sb.toString();
}

public StringBuffer notElimination() {
    final StringBuffer sb = new StringBuffer();
    sb.append("Hello, ").append("World!");
    return sb;
}

elimination1()中的鎖對象lock作用域只是方法內(nèi),沒有逃逸出線程,elimination2()中的sb也就這樣,所以這兩個方法的同步鎖都會被消除。但是notElimination()方法中的sb是方法返回值,可能會被其他方法修改或者其他線程修改,所以,單看這個方法,不會消除鎖,還得看調(diào)用方法。

鎖粗化(Lock Coarsening)

原則上,我們在編寫代碼的時候,要將同步塊作用域的作用范圍限制的盡量小。使得需要同步的操作數(shù)量盡量少,當存在鎖競爭時,等待線程盡快獲取鎖。但是有時候,如果一系列的連續(xù)操作都對同一個對象反復加鎖和解鎖,甚至加鎖操作是出現(xiàn)在循環(huán)體中的,那即使沒有出現(xiàn)線程競爭,頻繁地進行互斥同步操作也會導致不必要的性能損耗。如果虛擬機檢測到有一串零碎的操作都是對同一對象的加鎖,將會把加鎖同步的范圍擴展(粗化)到整個操作序列的外部。
比如上面例子中的elimination2()方法中,StringBuffer的append是同步方法,頻繁操作時,會進行鎖粗化,最后結(jié)果會類似于(只是類似,不是真實情況):

public String elimination2() {
    final StringBuilder sb = new StringBuilder();
    synchronized (sb) {
        sb.append("Hello, ").append("World!");
        return sb.toString();
    }
}

或者

public synchronized String elimination3() {
    final StringBuilder sb = new StringBuilder();
    sb.append("Hello, ").append("World!");
    return sb.toString();
}

文末總結(jié)

  • 同步操作中影響性能的有兩點:
    • 加鎖解鎖過程需要額外操作
    • 用戶態(tài)與內(nèi)核態(tài)之間轉(zhuǎn)換代價比較大
  • synchronized 在 JDK 1.6 中有大量優(yōu)化:分級鎖(偏向鎖、輕量級鎖、重量級鎖)、鎖消除、鎖粗化等。
  • synchronized 復用了對象頭的 Mark Word 狀態(tài)位,實現(xiàn)不同等級的鎖實現(xiàn)。

到此這篇關于Java實現(xiàn)synchronized鎖同步機制的文章就介紹到這了,更多相關Java synchronized鎖同步 內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關文章希望大家以后多多支持腳本之家!

相關文章

  • SpringBoot自動配置的原理詳解

    SpringBoot自動配置的原理詳解

    這篇文章主要介紹了SpringBoot自動配置的原理詳解,本節(jié)更詳細地介紹了如何使用 Spring Boot,它涵蓋了諸如構(gòu)建系統(tǒng)、自動配置以及如何運行應用程序等主題,我們還介紹了一些 Spring Boot 最佳實踐,需要的朋友可以參考下
    2023-09-09
  • @RequestBody不能映射到對象的解決

    @RequestBody不能映射到對象的解決

    這篇文章主要介紹了@RequestBody不能映射到對象的解決方案,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教
    2021-10-10
  • 淺析Java多線程同步synchronized

    淺析Java多線程同步synchronized

    本篇文章給大家詳細分析了Java多線程同步synchronized的相關知識點,需要的讀者們可以參考學習下。
    2018-02-02
  • 一文講透為什么遍歷LinkedList要用增強型for循環(huán)

    一文講透為什么遍歷LinkedList要用增強型for循環(huán)

    這篇文章主要為大家介紹了為什么遍歷LinkedList要用增強型for循環(huán)的透徹詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步,早日升職加薪
    2023-04-04
  • Java程序執(zhí)行Cmd指令所遇問題記錄及解決方案

    Java程序執(zhí)行Cmd指令所遇問題記錄及解決方案

    這篇文章主要介紹了Java程序執(zhí)行Cmd指令所遇問題記錄,本文給大家介紹的非常詳細,對大家的學習或工作具有一定的參考借鑒價值,需要的朋友可以參考下
    2020-09-09
  • Struts2中圖片以base64方式上傳至數(shù)據(jù)庫

    Struts2中圖片以base64方式上傳至數(shù)據(jù)庫

    這篇文章主要介紹了Struts2中圖片以base64方式上傳至數(shù)據(jù)庫的實現(xiàn)代碼,代碼分為前臺和后臺兩段,非常不錯,具有參考借鑒價值,需要的朋友可以參考下
    2016-09-09
  • Java多線程之scheduledThreadPool的方法解析

    Java多線程之scheduledThreadPool的方法解析

    這篇文章主要介紹了Java多線程之scheduledThreadPool的方法解析,queue是DelayedWorkQueue,但通過后面的分析可以知道,最大線程數(shù)是不起作用的,最多會起核心線程數(shù)的數(shù)量,需要的朋友可以參考下
    2023-12-12
  • 一篇文章帶你深入了解Java基礎

    一篇文章帶你深入了解Java基礎

    這篇文章主要給大家介紹了關于Java中方法使用的相關資料,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友們下面隨著小編來一起學習學習吧
    2021-08-08
  • 智能 AI 代碼生成工具 Cursor 安裝和使用超詳細教程

    智能 AI 代碼生成工具 Cursor 安裝和使用超詳細教程

    Cursor.so 是一個集成了 GPT-4 的國內(nèi)直接可以訪問的,優(yōu)秀而強大的免費代碼生成器,可以幫助你快速編寫、編輯和討論代碼,這篇文章主要介紹了智能 AI 代碼生成工具 Cursor 安裝和使用介紹,需要的朋友可以參考下
    2023-05-05
  • java仿Servlet生成驗證碼實例詳解

    java仿Servlet生成驗證碼實例詳解

    這篇文章主要介紹了java仿Servlet生成驗證碼實例詳解的相關資料,需要的朋友可以參考下
    2017-04-04

最新評論

杭州市| 依安县| 临海市| 稻城县| 慈利县| 石河子市| 锦屏县| 本溪市| 东山县| 苍南县| 康保县| 广元市| 威宁| 清丰县| 吉林省| 黄大仙区| 高陵县| 通许县| 香港| 迁西县| 高唐县| 蕉岭县| 和平县| 万盛区| 大厂| 西盟| 稻城县| 万安县| 疏勒县| 中宁县| 西畴县| 九台市| 嵊州市| 西乌珠穆沁旗| 泰顺县| 梨树县| 定西市| 大洼县| 裕民县| 昭觉县| 闵行区|