亚洲乱码中文字幕综合,中国熟女仑乱hd,亚洲精品乱拍国产一区二区三区,一本大道卡一卡二卡三乱码全集资源,又粗又黄又硬又爽的免费视频

java synchronized 鎖機(jī)制原理詳解

 更新時(shí)間:2021年08月27日 15:41:30   作者:張維鵬  
synchronized關(guān)鍵字是JAVA中常用的同步功能,提供了簡(jiǎn)單易用的鎖功能。這篇文章主要介紹了Java中synchronized關(guān)鍵字引出的多種鎖問(wèn)題,需要的朋友可以參考下

前言:

線程安全是并發(fā)編程中的重要關(guān)注點(diǎn),造成線程安全問(wèn)題的主要原因有兩點(diǎn),一是存在共享數(shù)據(jù)(也稱臨界資源),二是存在多條線程共同操作共享數(shù)據(jù)。因此為了解決這個(gè)問(wèn)題,我們可能需要這樣一個(gè)方案,當(dāng)存在多個(gè)線程操作共享數(shù)據(jù)時(shí),需要保證同一時(shí)刻有且只有一個(gè)線程在操作共享數(shù)據(jù),其他線程必須等到該線程處理完數(shù)據(jù)后再進(jìn)行,這種方式叫互斥鎖,即能達(dá)到互斥訪問(wèn)目的的鎖,也就是說(shuō)當(dāng)一個(gè)共享數(shù)據(jù)被當(dāng)前正在訪問(wèn)的線程加上互斥鎖后,在同一個(gè)時(shí)刻,其他線程只能處于等待的狀態(tài),直到當(dāng)前線程處理完畢釋放該鎖。

 1、synchronized 的作用:

synchronized 通過(guò)當(dāng)前線程持有對(duì)象鎖,從而擁有訪問(wèn)權(quán)限,而其他沒(méi)有持有當(dāng)前對(duì)象鎖的線程無(wú)法擁有訪問(wèn)權(quán)限,保證在同一時(shí)刻,只有一個(gè)線程可以執(zhí)行某個(gè)方法或者某個(gè)代碼塊,從而保證線程安全。synchronized 可以保證線程的可見(jiàn)性,synchronized 屬于隱式鎖,鎖的持有與釋放都是隱式的,我們無(wú)需干預(yù)。synchronized最主要的三種應(yīng)用方式:

  • 修飾實(shí)例方法:作用于當(dāng)前實(shí)例加鎖,進(jìn)入同步代碼前要獲得當(dāng)前實(shí)例的鎖
  • 修飾靜態(tài)方法:作用于當(dāng)前類對(duì)象加鎖,進(jìn)入同步代碼前要獲得當(dāng)前類對(duì)象的鎖
  • 修飾代碼塊:指定加鎖對(duì)象,進(jìn)入同步代碼庫(kù)前要獲得給定對(duì)象的鎖

2、synchronized 底層語(yǔ)義原理:

synchronized 鎖機(jī)制在 Java 虛擬機(jī)中的同步是基于進(jìn)入和退出監(jiān)視器鎖對(duì)象 monitor 實(shí)現(xiàn)的(無(wú)論是顯示同步還是隱式同步都是如此),每個(gè)對(duì)象的對(duì)象頭都關(guān)聯(lián)著一個(gè) monitor 對(duì)象,當(dāng)一個(gè) monitor 被某個(gè)線程持有后,它便處于鎖定狀態(tài)。在 HotSpot 虛擬機(jī)中,monitor 是由 ObjectMonitor 實(shí)現(xiàn)的,每個(gè)等待鎖的線程都會(huì)被封裝成 ObjectWaiter 對(duì)象,ObjectMonitor 中有兩個(gè)集合,WaitSet 和 _EntryList,用來(lái)保存 ObjectWaiter 對(duì)象列表 ,owner 區(qū)域指向持有 ObjectMonitor 對(duì)象的線程。當(dāng)多個(gè)線程同時(shí)訪問(wèn)一段同步代碼時(shí),首先會(huì)進(jìn)入 _EntryList 集合嘗試獲取 moniter,當(dāng)線程獲取到對(duì)象的 monitor 后進(jìn)入 _Owner 區(qū)域并把 _owner 變量設(shè)置為當(dāng)前線程,同時(shí) monitor 中的計(jì)數(shù)器 count 加1;若線程調(diào)用 wait() 方法,將釋放當(dāng)前持有的 monitor,count自減1,owner 變量恢復(fù)為 null,同時(shí)該線程進(jìn)入 _WaitSet 集合中等待被喚醒。若當(dāng)前線程執(zhí)行完畢也將釋放 monitor 并復(fù)位變量的值,以便其他線程獲取 monitor。如下圖所示:

3、 synchronized 的顯式同步與隱式同步:

synchronized 分為顯式同步(同步代碼塊)和隱式同步(同步方法),顯式同步指的是有明確的 monitorenter 和 monitorexit 指令,而隱式同步并不是由 monitorenter 和 monitorexit 指令來(lái)實(shí)現(xiàn)同步的,而是由方法調(diào)用指令讀取運(yùn)行時(shí)常量池中方法的 ACC_SYNCHRONIZED 標(biāo)志來(lái)隱式實(shí)現(xiàn)的。

3.1、synchronized 代碼塊底層原理:

synchronized 同步語(yǔ)句塊的實(shí)現(xiàn)是顯式同步的,通過(guò) monitorenter 和 monitorexit 指令實(shí)現(xiàn),其中 monitorenter 指令指向同步代碼塊的開始位置,monitorexit 指令則指明同步代碼塊的結(jié)束位置,當(dāng)執(zhí)行 monitorenter 指令時(shí),當(dāng)前線程將嘗試獲取 objectref(即對(duì)象鎖)所對(duì)應(yīng)的 monitor 的持有權(quán):

  • 當(dāng)對(duì)象鎖的 monitor 的進(jìn)入計(jì)數(shù)器為 0,那線程可以成功取得 monitor,并將計(jì)數(shù)器值設(shè)置為 1,取鎖成功。
  • 如果當(dāng)前線程已經(jīng)擁有對(duì)象鎖的 monitor 的持有權(quán),那它可以重入這個(gè) monitor,重入時(shí)計(jì)數(shù)器的值也會(huì)加1。
  • 若其他線程已經(jīng)擁有對(duì)象鎖的 monitor 的所有權(quán),那當(dāng)前線程將被阻塞,直到正在執(zhí)行線程執(zhí)行完畢,即monitorexit 指令被執(zhí)行,執(zhí)行線程將釋放 monitor 并設(shè)置計(jì)數(shù)器值為0,其他線程將有機(jī)會(huì)持有 monitor。

編譯器會(huì)確保無(wú)論方法通過(guò)何種方式完成,無(wú)論是正常結(jié)束還是異常結(jié)束,代碼中調(diào)用過(guò)的每條 monitorenter 指令都有執(zhí)行其對(duì)應(yīng) monitorexit 指令。為了保證在方法異常完成時(shí),monitorenter 和 monitorexit 指令依然可以正確配對(duì)執(zhí)行,編譯器會(huì)自動(dòng)產(chǎn)生一個(gè)異常處理器,這個(gè)異常處理器可處理所有的異常,它的目的就是用來(lái)執(zhí)行 monitorexit 指令。

3.2、synchronized 方法底層原理:

synchronized 同步方法的實(shí)現(xiàn)是隱式的,無(wú)需通過(guò)字節(jié)碼指令來(lái)控制,它是在方法調(diào)用和返回操作之中實(shí)現(xiàn)。JVM 可以通過(guò)方法常量池中的方法表結(jié)構(gòu)(method_info Structure)中的 ACC_SYNCHRONIZED 訪問(wèn)標(biāo)志 判斷一個(gè)方法是否同步方法。當(dāng)方法調(diào)用時(shí),調(diào)用指令將會(huì)檢查方法的 ACC_SYNCHRONIZED 訪問(wèn)標(biāo)志是否被設(shè)置,如果設(shè)置了,標(biāo)識(shí)該方法是一個(gè)同步方法,執(zhí)行線程將先持有 monitor, 然后再執(zhí)行方法,最后再方法完成(無(wú)論是正常完成還是非正常完成)時(shí)釋放 monitor。在方法執(zhí)行期間,執(zhí)行線程持有了 monitor,其他任何線程都無(wú)法再獲得同一個(gè) monitor。

 如果一個(gè)同步方法執(zhí)行期間拋出了異常,并且在方法內(nèi)部無(wú)法處理此異常,那這個(gè)同步方法所持有的 monitor 將在異常拋到同步方法之外時(shí)自動(dòng)釋放。

 4、JVM 對(duì) synchronized 鎖的優(yōu)化:

在早期版本中,synchronized 屬于重量級(jí)鎖,效率低下,因?yàn)楸O(jiān)視器鎖 monitor 是依賴于操作系統(tǒng)的 Mutex 互斥量來(lái)實(shí)現(xiàn)的,操作系統(tǒng)實(shí)現(xiàn)線程之間的切換時(shí)需要從用戶態(tài)轉(zhuǎn)換到核心態(tài),這個(gè)狀態(tài)之間的轉(zhuǎn)換需要相對(duì)比較長(zhǎng)的時(shí)間,時(shí)間成本相對(duì)較高。在 JDK6 之后,synchronized 在 JVM 層面做了優(yōu)化,減少鎖的獲取和釋放所帶來(lái)的性能消耗,主要優(yōu)化方向有以下幾點(diǎn):

4.1、鎖升級(jí):偏向鎖->輕量級(jí)鎖->自旋鎖->重量級(jí)鎖

鎖的狀態(tài)總共有四種,無(wú)鎖狀態(tài)、偏向鎖、輕量級(jí)鎖和重量級(jí)鎖。隨著鎖的競(jìng)爭(zhēng),鎖可以從偏向鎖升級(jí)到輕量級(jí)鎖,再升級(jí)的重量級(jí)鎖,但是鎖的升級(jí)是單向的,只能從低到高升級(jí),不會(huì)出現(xiàn)鎖的降級(jí)。重量級(jí)鎖基于從操作系統(tǒng)的互斥量實(shí)現(xiàn)的,而偏向鎖與輕量級(jí)鎖不同,他們是通過(guò) CAS 并配合 Mark Word 一起實(shí)現(xiàn)的。

4.1.1、synchronized 的 Mark word 標(biāo)志位:

synchronized 使用的鎖對(duì)象是存儲(chǔ)在 Java 對(duì)象頭里的,那么 Java 對(duì)象頭是什么呢?對(duì)象實(shí)例分為:

  • 對(duì)象頭
    • Mark Word
    • 指向類的指針
    • 數(shù)組長(zhǎng)度
  • 實(shí)例數(shù)據(jù)
  • 對(duì)齊填充

其中,Mark Word 記錄了對(duì)象的 hashcode、分代年齡、鎖標(biāo)記位相關(guān)的信息,由于對(duì)象頭的信息是與對(duì)象自身定義的數(shù)據(jù)沒(méi)有關(guān)系的額外存儲(chǔ)成本,因此考慮到 JVM 的空間效率,Mark Word 被設(shè)計(jì)成為一個(gè)非固定的數(shù)據(jù)結(jié)構(gòu),以便存儲(chǔ)更多有效的數(shù)據(jù),它會(huì)根據(jù)對(duì)象本身的狀態(tài)復(fù)用自己的存儲(chǔ)空間,在 32位 JVM 中的長(zhǎng)度是 32 位,具體信息如下圖所示:

4.1.2、鎖升級(jí)過(guò)程:

(1)偏向鎖:如果一個(gè)線程獲得了鎖,那么進(jìn)入偏向模式,當(dāng)這個(gè)線程再次請(qǐng)求鎖的時(shí)候,只需去對(duì)象頭的 Mark Word 中判斷偏向線程ID是否指向它自己,無(wú)需再進(jìn)入 monitor 中去競(jìng)爭(zhēng)對(duì)象,這樣就省去了大量鎖申請(qǐng)的操作,適用于連續(xù)多次都是同一個(gè)線程申請(qǐng)相同的鎖的場(chǎng)景。偏向鎖只有初始化的時(shí)候需要一次 CAS 操作,但如果出現(xiàn)其他線程競(jìng)爭(zhēng)鎖資源,那么偏向鎖就會(huì)被撤銷,并升級(jí)為輕量級(jí)鎖。

(2)輕量級(jí)鎖:不需要申請(qǐng)互斥量,允許短時(shí)間內(nèi)的鎖競(jìng)爭(zhēng),每次申請(qǐng)、釋放鎖都至少需要一次 CAS,適用于多個(gè)線程交替執(zhí)行同步代碼塊的場(chǎng)景

(3)自旋鎖:自旋鎖假設(shè)在不久將來(lái),當(dāng)前的線程可以獲得鎖,因此在輕量級(jí)鎖升級(jí)成為重量級(jí)鎖之前,虛擬機(jī)會(huì)讓當(dāng)前想要獲取鎖的線程做幾個(gè)空循環(huán),在經(jīng)過(guò)若干次循環(huán)后,如果得到鎖,就順利進(jìn)入臨界區(qū),如果還不能獲得鎖,那就會(huì)將線程在操作系統(tǒng)層面掛起。

這種方式確實(shí)可以提升效率的,但是當(dāng)線程越來(lái)越多競(jìng)爭(zhēng)很激烈時(shí),占用 CPU 的時(shí)間變長(zhǎng)會(huì)導(dǎo)致性能急劇下降,因此 JVM 對(duì)于自旋鎖有一定的次數(shù)限制,可能是50或者100次循環(huán)后就放棄,直接掛起線程,讓出CPU資源。

(4)自適應(yīng)自旋鎖:自適應(yīng)自旋解決的是 “鎖競(jìng)爭(zhēng)時(shí)間不確定” 的問(wèn)題,自適應(yīng)意味著自旋的時(shí)間不再固定了,而是由前一次在同一個(gè)鎖上的自旋時(shí)間及鎖的擁有者的狀態(tài)來(lái)決定。

  • 如果在同一個(gè)鎖對(duì)象上,自旋等待剛剛成功獲得過(guò)鎖,并且持有鎖的線程正在運(yùn)行中,那么虛擬機(jī)就會(huì)認(rèn)為這次自旋也很有可能再次成功,進(jìn)而它將允許自旋等待持續(xù)相對(duì)更長(zhǎng)的時(shí)間,比如100個(gè)循環(huán)。
  • 相反的,如果對(duì)于某個(gè)鎖,自旋很少成功獲得過(guò),那在以后要獲取這個(gè)鎖時(shí)將可能減少自旋時(shí)間甚至省略自旋過(guò)程,以避免浪費(fèi)處理器資源。

但自旋鎖帶來(lái)的副作用就是不公平的鎖機(jī)制:處于阻塞狀態(tài)的線程,并沒(méi)有辦法立刻競(jìng)爭(zhēng)被釋放的鎖。然而,處于自旋狀態(tài)的線程,則很有可能優(yōu)先獲得這把鎖。

(5)重量級(jí)鎖:適用于多個(gè)線程同時(shí)執(zhí)行同步代碼塊的場(chǎng)景,且鎖競(jìng)爭(zhēng)時(shí)間長(zhǎng)。在這個(gè)狀態(tài)下,未搶到鎖的線程都會(huì)進(jìn)入到 Monitor 中并阻塞在 _WaitSet 集合中。

4.2、鎖消除:

消除鎖屬于編譯器對(duì)鎖的優(yōu)化,JIT 編譯時(shí)(可以簡(jiǎn)單理解為當(dāng)某段代碼即將第一次被執(zhí)行時(shí)進(jìn)行編譯,又稱即時(shí)編譯)會(huì)使用逃逸分析技術(shù),通過(guò)對(duì)運(yùn)行上下文的掃描,去除不可能存在共享資源競(jìng)爭(zhēng)的鎖,通過(guò)這種方式消除沒(méi)有必要的鎖,可以節(jié)省毫無(wú)意義的請(qǐng)求鎖時(shí)間。

4.3、鎖粗化:

JIT 編譯器動(dòng)態(tài)編譯時(shí),如果發(fā)現(xiàn)幾個(gè)相鄰的同步塊使用的是同一個(gè)鎖實(shí)例,那么 JIT 編譯器將會(huì)把這幾個(gè)同步塊合并為一個(gè)大的同步塊,從而避免一個(gè)線程“反復(fù)申請(qǐng)、釋放同一個(gè)鎖“所帶來(lái)的性能開銷。

5、偏向鎖的廢除:

 在 JDK6 中引入的偏向鎖能夠減少競(jìng)爭(zhēng)鎖定的開銷,使得 JVM 的性能得到了顯著改善,但是 JDK15 卻將決定將偏向鎖禁用,并在以后刪除它,這是為什么呢?主要有以下幾個(gè)原因:

  • 為了支持偏向鎖使得代碼復(fù)雜度大幅度提升,并且對(duì) HotSpot 的其他組件產(chǎn)生了影響,這種復(fù)雜性已成為理解代碼的障礙,也阻礙了對(duì)同步系統(tǒng)進(jìn)行重構(gòu)
  • 在更高的 JDK 版本中針對(duì)多線程場(chǎng)景推出了性能更高的并發(fā)數(shù)據(jù)結(jié)構(gòu),所以過(guò)去看到的性能提升,在現(xiàn)在看來(lái)已經(jīng)不那么明顯了。
  • 圍繞線程池隊(duì)列和工作線程構(gòu)建的應(yīng)用程序,性能通常在禁用偏向鎖的情況下變得更好。

鎖升級(jí)過(guò)程詳細(xì)解析推薦閱讀:http://chabaoo.cn/article/186708.htm

參考文章://chabaoo.cn/article/221033.htm

總結(jié)

本篇文章就到這里了,希望能給你帶來(lái)幫助,也希望您能夠多多關(guān)注腳本之家的更多內(nèi)容!

相關(guān)文章

  • 如何計(jì)算Java對(duì)象占用了多少空間?

    如何計(jì)算Java對(duì)象占用了多少空間?

    在Java中沒(méi)有sizeof運(yùn)算符,所以沒(méi)辦法知道一個(gè)對(duì)象到底占用了多大的空間,但是在分配對(duì)象的時(shí)候會(huì)有一些基本的規(guī)則,我們根據(jù)這些規(guī)則大致能判斷出來(lái)對(duì)象大小,需要的朋友可以參考下
    2016-01-01
  • 在idea中g(shù)it pull失敗的解決方案

    在idea中g(shù)it pull失敗的解決方案

    在遇到Git Pull失敗時(shí),首先使用IDEA的git-revert功能進(jìn)行還原,然后檢查并解決分支沖突,最后重新執(zhí)行Git Pull確保所有文件是最新的,注意,在操作過(guò)程中確保網(wǎng)絡(luò)連接正常,并且每步操作后都要執(zhí)行Git Pull來(lái)更新數(shù)據(jù)
    2024-10-10
  • Mybatis-plus 雙主鍵的實(shí)現(xiàn)示例

    Mybatis-plus 雙主鍵的實(shí)現(xiàn)示例

    本文主要介紹了Mybatis-plus 雙主鍵的實(shí)現(xiàn)示例,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧
    2024-05-05
  • MyBatisPlus3.4.3版自動(dòng)生成代碼的使用過(guò)程

    MyBatisPlus3.4.3版自動(dòng)生成代碼的使用過(guò)程

    這篇文章主要介紹了MyBatisPlus3.4.3版自動(dòng)生成代碼的使用,本文通過(guò)實(shí)例代碼給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下
    2023-04-04
  • Spring注解@Conditional案例解析

    Spring注解@Conditional案例解析

    這篇文章主要介紹了Spring注解@Conditional案例解析,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下
    2019-10-10
  • JavaWeb使用Cookie模擬實(shí)現(xiàn)自動(dòng)登錄功能(不需用戶名和密碼)

    JavaWeb使用Cookie模擬實(shí)現(xiàn)自動(dòng)登錄功能(不需用戶名和密碼)

    不需要填寫用戶名和密碼自動(dòng)登錄系統(tǒng),其實(shí)現(xiàn)思路使用cookie模擬瀏覽器自動(dòng)登錄,對(duì)cookie實(shí)現(xiàn)自動(dòng)登錄功能感興趣的朋友一起學(xué)習(xí)吧
    2016-08-08
  • intellij idea快速查看當(dāng)前類中的所有方法(推薦)

    intellij idea快速查看當(dāng)前類中的所有方法(推薦)

    這篇文章主要介紹了intellij idea快速查看當(dāng)前類中的所有方法,本文通過(guò)實(shí)例代碼給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下
    2020-09-09
  • 淺談JAVA工作流的優(yōu)雅實(shí)現(xiàn)方式

    淺談JAVA工作流的優(yōu)雅實(shí)現(xiàn)方式

    這篇文章主要介紹了淺談JAVA工作流的優(yōu)雅實(shí)現(xiàn)方式,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧
    2018-11-11
  • Linux環(huán)境卸載Centos7自帶的OpenJDK和安裝JDK1.8圖文教程

    Linux環(huán)境卸載Centos7自帶的OpenJDK和安裝JDK1.8圖文教程

    CentOS系統(tǒng)是開發(fā)者常用的Linux操作系統(tǒng),安裝它時(shí)會(huì)默認(rèn)安裝自帶的舊版本的OpenJDK,但在開發(fā)者平時(shí)開發(fā)Java項(xiàng)目時(shí)還是需要完整的JDK,這篇文章主要給大家介紹了關(guān)于Linux環(huán)境卸載Centos7自帶的OpenJDK和安裝JDK1.8的相關(guān)資料,需要的朋友可以參考下
    2024-07-07
  • 全面解析SpringBoot文件上傳功能

    全面解析SpringBoot文件上傳功能

    這篇文章主要為大家全面解析SpringBoot文件上傳功能,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下
    2017-11-11

最新評(píng)論