<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          超多干貨!synchronized 知識(shí)揭秘

          共 8777字,需瀏覽 18分鐘

           ·

          2021-06-12 17:15

          fa5e21939ddc0197c9b170be9f8da0ea.webp

          大家好,我是躍哥。synchronized 平時(shí)大家用的多嗎?大家知道他的作用嗎?知道如何更好的使用嗎?今天,就接著 cxuan 的文章,我們一起來(lái)揭秘。

          synchronized 這個(gè)關(guān)鍵字的重要性不言而喻,幾乎可以說(shuō)是并發(fā)、多線程必須會(huì)問(wèn)到的關(guān)鍵字了。synchronized 會(huì)涉及到鎖、升級(jí)降級(jí)操作、鎖的撤銷、對(duì)象頭等。所以理解 synchronized 非常重要,本篇文章就帶你從 synchronized 的基本用法、再到 synchronized 的深入理解,對(duì)象頭等,為你揭開(kāi) synchronized 的面紗。

          淺析 synchronized

          synchronized?是 Java?并發(fā)模塊?非常重要的關(guān)鍵字,它是 Java 內(nèi)建的一種同步機(jī)制,代表了某種內(nèi)在鎖定的概念,當(dāng)一個(gè)線程對(duì)某個(gè)共享資源加鎖后,其他想要獲取共享資源的線程必須進(jìn)行等待,synchronized 也具有互斥和排他的語(yǔ)義。

          什么是互斥?我們想必小時(shí)候都玩兒過(guò)磁鐵,磁鐵會(huì)有正負(fù)極的概念,同性相斥異性相吸,相斥相當(dāng)于就是一種互斥的概念,也就是兩者互不相容。

          synchronized 也是一種獨(dú)占的關(guān)鍵字,但是它這種獨(dú)占的語(yǔ)義更多的是為了增加線程安全性,通過(guò)獨(dú)占某個(gè)資源以達(dá)到互斥、排他的目的。

          在了解了排他和互斥的語(yǔ)義后,我們先來(lái)看一下 synchronized 的用法,先來(lái)了解用法,再來(lái)了解底層實(shí)現(xiàn)。

          synchronized 的使用

          關(guān)于 synchronized 想必你應(yīng)該都大致了解過(guò)

          • synchronized 修飾實(shí)例方法,相當(dāng)于是對(duì)類的實(shí)例進(jìn)行加鎖,進(jìn)入同步代碼前需要獲得當(dāng)前實(shí)例的鎖

          • synchronized 修飾靜態(tài)方法,相當(dāng)于是對(duì)類對(duì)象進(jìn)行加鎖

          • synchronized 修飾代碼塊,相當(dāng)于是給對(duì)象進(jìn)行加鎖,在進(jìn)入代碼塊前需要先獲得對(duì)象的鎖

          下面我們針對(duì)每個(gè)用法進(jìn)行解釋

          synchronized 修飾實(shí)例方法

          synchronized 修飾實(shí)例方法,實(shí)例方法是屬于類的實(shí)例。synchronized 修飾的實(shí)例方法相當(dāng)于是對(duì)象鎖。下面是一個(gè) synchronized 修飾實(shí)例方法的例子。

          public?synchronized?void?method()
          {
          ???//?...
          }

          像如上述 synchronized 修飾的方法就是實(shí)例方法,下面我們通過(guò)一個(gè)完整的例子來(lái)認(rèn)識(shí)一下 synchronized 修飾實(shí)例方法

          public?class?TSynchronized?implements?Runnable{

          ????static?int?i?=?0;

          ????public?synchronized?void?increase(){
          ????????i++;
          ????????System.out.println(Thread.currentThread().getName());
          ????}


          ????@Override
          ????public?void?run()?{
          ????????for(int?i?=?0;i?<?1000;i++)?{
          ????????????increase();
          ????????}
          ????}

          ????public?static?void?main(String[]?args)?throws?InterruptedException?{

          ????????TSynchronized?tSynchronized?=?new?TSynchronized();
          ????????Thread?aThread?=?new?Thread(tSynchronized);
          ????????Thread?bThread?=?new?Thread(tSynchronized);
          ????????aThread.start();
          ????????bThread.start();
          ????????aThread.join();
          ????????bThread.join();
          ????????System.out.println("i?=?"?+?i);
          ????}
          }

          上面輸出的結(jié)果 i = 2000 ,并且每次都會(huì)打印當(dāng)前現(xiàn)成的名字

          來(lái)解釋一下上面代碼,代碼中的 i 是一個(gè)靜態(tài)變量,靜態(tài)變量也是全局變量,靜態(tài)變量存儲(chǔ)在方法區(qū)中。increase 方法由 synchronized 關(guān)鍵字修飾,但是沒(méi)有使用 static 關(guān)鍵字修飾,表示 increase 方法是一個(gè)實(shí)例方法,每次創(chuàng)建一個(gè) TSynchronized 類的同時(shí)都會(huì)創(chuàng)建一個(gè) increase 方法,increase 方法中只是打印出來(lái)了當(dāng)前訪問(wèn)的線程名稱。Synchronized 類實(shí)現(xiàn)了 Runnable 接口,重寫(xiě)了 run 方法,run 方法里面就是一個(gè) 0 - 1000 的計(jì)數(shù)器,這個(gè)沒(méi)什么好說(shuō)的。在 main 方法中,new 出了兩個(gè)線程,分別是 aThread 和 bThread,Thread.join 表示等待這個(gè)線程處理結(jié)束。這段代碼主要的作用就是判斷 synchronized 修飾的方法能夠具有獨(dú)占性。

          synchronized 修飾靜態(tài)方法

          synchronized 修飾靜態(tài)方法就是 synchronized 和 static 關(guān)鍵字一起使用

          public?static?synchronized?void?increase(){}

          當(dāng) synchronized 作用于靜態(tài)方法時(shí),表示的就是當(dāng)前類的鎖,因?yàn)殪o態(tài)方法是屬于類的,它不屬于任何一個(gè)實(shí)例成員,因此可以通過(guò) class 對(duì)象控制并發(fā)訪問(wèn)。

          這里需要注意一點(diǎn),因?yàn)?synchronized 修飾的實(shí)例方法是屬于實(shí)例對(duì)象,而 synchronized 修飾的靜態(tài)方法是屬于類對(duì)象,所以調(diào)用 synchronized 的實(shí)例方法并不會(huì)阻止訪問(wèn) synchronized 的靜態(tài)方法。

          synchronized 修飾代碼塊

          synchronized 除了修飾實(shí)例方法和靜態(tài)方法外,synchronized 還可用于修飾代碼塊,代碼塊可以嵌套在方法體的內(nèi)部使用。

          public?void?run()?{
          ??synchronized(obj){
          ????for(int?j?=?0;j?<?1000;j++){
          ??????i++;
          ????}
          ??}
          }

          上面代碼中將 obj 作為鎖對(duì)象對(duì)其加鎖,每次當(dāng)線程進(jìn)入 synchronized 修飾的代碼塊時(shí)就會(huì)要求當(dāng)前線程持有obj 實(shí)例對(duì)象鎖,如果當(dāng)前有其他線程正持有該對(duì)象鎖,那么新到的線程就必須等待。

          synchronized 修飾的代碼塊,除了可以鎖定對(duì)象之外,也可以對(duì)當(dāng)前實(shí)例對(duì)象鎖、class 對(duì)象鎖進(jìn)行鎖定

          //?實(shí)例對(duì)象鎖
          synchronized(this){
          ????for(int?j?=?0;j?<?1000;j++){
          ????????i++;
          ????}
          }

          //class對(duì)象鎖
          synchronized(TSynchronized.class){
          ????for(int?j?=?0;j?<?1000;j++){
          ????????i++;
          ????}
          }

          synchronized 底層原理

          在簡(jiǎn)單介紹完 synchronized 之后,我們就來(lái)聊一下 synchronized 的底層原理了。

          我們或許都有所了解(下文會(huì)細(xì)致分析),synchronized 的代碼塊是由一組 monitorenter/monitorexit 指令實(shí)現(xiàn)的。而Monitor?對(duì)象是實(shí)現(xiàn)同步的基本單元。

          啥是?Monitor?對(duì)象呢?

          Monitor 對(duì)象

          任何對(duì)象都關(guān)聯(lián)了一個(gè)管程,管程就是控制對(duì)象并發(fā)訪問(wèn)的一種機(jī)制。管程?是一種同步原語(yǔ),在 Java 中指的就是 synchronized,可以理解為 synchronized 就是 Java 中對(duì)管程的實(shí)現(xiàn)。

          管程提供了一種排他訪問(wèn)機(jī)制,這種機(jī)制也就是?互斥?;コ獗WC了在每個(gè)時(shí)間點(diǎn)上,最多只有一個(gè)線程會(huì)執(zhí)行同步方法。

          所以你理解了 Monitor 對(duì)象其實(shí)就是使用管程控制同步訪問(wèn)的一種對(duì)象。

          對(duì)象內(nèi)存布局

          在?hotspot?虛擬機(jī)中,對(duì)象在內(nèi)存中的布局分為三塊區(qū)域:

          • 對(duì)象頭(Header)

          • 實(shí)例數(shù)據(jù)(Instance Data)

          • 對(duì)齊填充(Padding)

          這三塊區(qū)域的內(nèi)存分布如下圖所示

          b6475c1ee5523e3f13389fe21579e17f.webp

          我們來(lái)詳細(xì)介紹一下上面對(duì)象中的內(nèi)容。

          對(duì)象頭 Header

          對(duì)象頭 Header 主要包含 MarkWord 和對(duì)象指針 Klass Pointer,如果是數(shù)組的話,還要包含數(shù)組的長(zhǎng)度。

          87e6acd0465e30b637b54ed927bf4d6e.webp

          在 32 位的虛擬機(jī)中 MarkWord ,Klass Pointer 和數(shù)組長(zhǎng)度分別占用 32 位,也就是 4 字節(jié)。

          如果是 64 位虛擬機(jī)的話,MarkWord ,Klass Pointer 和數(shù)組長(zhǎng)度分別占用 64 位,也就是 8 字節(jié)。

          在 32 位虛擬機(jī)和 64 位虛擬機(jī)的 Mark Word 所占用的字節(jié)大小不一樣,32 位虛擬機(jī)的 Mark Word 和 Klass Pointer 分別占用 32 bits 的字節(jié),而 64 位虛擬機(jī)的 Mark Word 和 Klass Pointer 占用了64 bits 的字節(jié),下面我們以 32 位虛擬機(jī)為例,來(lái)看一下其 Mark Word 的字節(jié)具體是如何分配的。

          d2b0d5959e5b14f6966ba855a6f71561.webpbe68f7acc33abd3c7198161d17f88117.webp

          用中文翻譯過(guò)來(lái)就是

          e1d48e7533bd8fbe0a0dd879b3058866.webp
          • 無(wú)狀態(tài)也就是無(wú)鎖的時(shí)候,對(duì)象頭開(kāi)辟 25 bit 的空間用來(lái)存儲(chǔ)對(duì)象的 hashcode ,4 bit 用于存放分代年齡,1 bit 用來(lái)存放是否偏向鎖的標(biāo)識(shí)位,2 bit 用來(lái)存放鎖標(biāo)識(shí)位為 01。

          • 偏向鎖?中劃分更細(xì),還是開(kāi)辟 25 bit 的空間,其中 23 bit 用來(lái)存放線程ID,2bit 用來(lái)存放 epoch,4bit 存放分代年齡,1 bit 存放是否偏向鎖標(biāo)識(shí), 0 表示無(wú)鎖,1 表示偏向鎖,鎖的標(biāo)識(shí)位還是 01。

          • 輕量級(jí)鎖中直接開(kāi)辟 30 bit 的空間存放指向棧中鎖記錄的指針,2bit 存放鎖的標(biāo)志位,其標(biāo)志位為 00。

          • 重量級(jí)鎖中和輕量級(jí)鎖一樣,30 bit 的空間用來(lái)存放指向重量級(jí)鎖的指針,2 bit 存放鎖的標(biāo)識(shí)位,為 11

          • GC標(biāo)記開(kāi)辟 30 bit 的內(nèi)存空間卻沒(méi)有占用,2 bit 空間存放鎖標(biāo)志位為 11。

          其中無(wú)鎖和偏向鎖的鎖標(biāo)志位都是 01,只是在前面的 1 bit 區(qū)分了這是無(wú)鎖狀態(tài)還是偏向鎖狀態(tài)。

          關(guān)于為什么這么分配的內(nèi)存,我們可以從?OpenJDK?中的markOop.hpp類中的枚舉窺出端倪

          cd8ce76430cee678f7c419a881fb2dac.webp

          來(lái)解釋一下

          • age_bits 就是我們說(shuō)的分代回收的標(biāo)識(shí),占用4字節(jié)

          • lock_bits 是鎖的標(biāo)志位,占用2個(gè)字節(jié)

          • biased_lock_bits 是是否偏向鎖的標(biāo)識(shí),占用1個(gè)字節(jié)。

          • max_hash_bits 是針對(duì)無(wú)鎖計(jì)算的 hashcode 占用字節(jié)數(shù)量,如果是 32 位虛擬機(jī),就是 32 - 4 - 2 -1 = 25 byte,如果是 64 位虛擬機(jī),64 - 4 - 2 - 1 = 57 byte,但是會(huì)有 25 字節(jié)未使用,所以 64 位的 hashcode 占用 31 byte。

          • hash_bits 是針對(duì) 64 位虛擬機(jī)來(lái)說(shuō),如果最大字節(jié)數(shù)大于 31,則取 31,否則取真實(shí)的字節(jié)數(shù)

          • cms_bits 我覺(jué)得應(yīng)該是不是 64 位虛擬機(jī)就占用 0 byte,是 64 位就占用 1byte

          • epoch_bits 就是 epoch 所占用的字節(jié)大小,2 字節(jié)。

          在上面的虛擬機(jī)對(duì)象頭分配表中,我們可以看到有幾種鎖的狀態(tài):無(wú)鎖(無(wú)狀態(tài)),偏向鎖,輕量級(jí)鎖,重量級(jí)鎖,其中輕量級(jí)鎖和偏向鎖是 JDK1.6 中對(duì) synchronized 鎖進(jìn)行優(yōu)化后新增加的,其目的就是為了大大優(yōu)化鎖的性能,所以在 JDK 1.6 中,使用 synchronized 的開(kāi)銷也沒(méi)那么大了。其實(shí)從鎖有無(wú)鎖定來(lái)講,還是只有無(wú)鎖和重量級(jí)鎖,偏向鎖和輕量級(jí)鎖的出現(xiàn)就是增加了鎖的獲取性能而已,并沒(méi)有出現(xiàn)新的鎖。

          所以我們的重點(diǎn)放在對(duì) synchronized 重量級(jí)鎖的研究上,當(dāng) monitor 被某個(gè)線程持有后,它就會(huì)處于鎖定狀態(tài)。在 HotSpot 虛擬機(jī)中,monitor 的底層代碼是由?ObjectMonitor?實(shí)現(xiàn)的,其主要數(shù)據(jù)結(jié)構(gòu)如下(位于 HotSpot 虛擬機(jī)源碼 ObjectMonitor.hpp 文件,C++ 實(shí)現(xiàn)的)

          2a62068af7106c686c93b7603c20d496.webp

          這段 C++ 中需要注意幾個(gè)屬性:_WaitSet 、 _EntryList 和 _Owner,每個(gè)等待獲取鎖的線程都會(huì)被封裝稱為?ObjectWaiter?對(duì)象。

          6ee855eb7a7c5d3b1b84c5e059527f09.webp

          _Owner 是指向了 ObjectMonitor 對(duì)象的線程,而 _WaitSet 和 _EntryList 就是用來(lái)保存每個(gè)線程的列表。

          那么這兩個(gè)列表有什么區(qū)別呢?這個(gè)問(wèn)題我和你聊一下鎖的獲取流程你就清楚了。

          鎖的兩個(gè)列表

          當(dāng)多個(gè)線程同時(shí)訪問(wèn)某段同步代碼時(shí),首先會(huì)進(jìn)入 _EntryList 集合,當(dāng)線程獲取到對(duì)象的 monitor 之后,就會(huì)進(jìn)入 _Owner 區(qū)域,并把 ObjectMonitor 對(duì)象的 _Owner 指向?yàn)楫?dāng)前線程,并使 _count + 1,如果調(diào)用了釋放鎖(比如 wait)的操作,就會(huì)釋放當(dāng)前持有的 monitor ,owner = null, _count - 1,同時(shí)這個(gè)線程會(huì)進(jìn)入到 _WaitSet 列表中等待被喚醒。如果當(dāng)前線程執(zhí)行完畢后也會(huì)釋放 monitor 鎖,只不過(guò)此時(shí)不會(huì)進(jìn)入 _WaitSet 列表了,而是直接復(fù)位 _count 的值。

          f06b72a1ecc5ac9e99b1cea6c12980ed.webp

          Klass Pointer 表示的是類型指針,也就是對(duì)象指向它的類元數(shù)據(jù)的指針,虛擬機(jī)通過(guò)這個(gè)指針來(lái)確定這個(gè)對(duì)象是哪個(gè)類的實(shí)例。

          你可能不是很理解指針是個(gè)什么概念,你可以簡(jiǎn)單理解為指針就是指向某個(gè)數(shù)據(jù)的地址。

          e586ddc10b7ae9562f95bd23fd6419fd.webp

          實(shí)例數(shù)據(jù) Instance Data

          實(shí)例數(shù)據(jù)部分是對(duì)象真正存儲(chǔ)的有效信息,也是代碼中定義的各個(gè)字段的字節(jié)大小,比如一個(gè) byte 占 1 個(gè)字節(jié),一個(gè) int 占用 4 個(gè)字節(jié)。

          對(duì)齊 Padding

          對(duì)齊不是必須存在的,它只起到了占位符(%d, %c 等)的作用。這就是 JVM 的要求了,因?yàn)?HotSpot JVM 要求對(duì)象的起始地址必須是 8 字節(jié)的整數(shù)倍,也就是說(shuō)對(duì)象的字節(jié)大小是 8 的整數(shù)倍,不夠的需要使用 Padding 補(bǔ)全。

          鎖的升級(jí)流程

          先來(lái)個(gè)大體的流程圖來(lái)感受一下這個(gè)過(guò)程,然后下面我們?cè)俜珠_(kāi)來(lái)說(shuō)

          330b5774916df2d34425ec34bbc01c52.webp

          無(wú)鎖

          無(wú)鎖狀態(tài),無(wú)鎖即沒(méi)有對(duì)資源進(jìn)行鎖定,所有的線程都可以對(duì)同一個(gè)資源進(jìn)行訪問(wèn),但是只有一個(gè)線程能夠成功修改資源。

          0cc15ec6ee76ff7912f08ceea2037e61.webp

          無(wú)鎖的特點(diǎn)就是在循環(huán)內(nèi)進(jìn)行修改操作,線程會(huì)不斷的嘗試修改共享資源,直到能夠成功修改資源并退出,在此過(guò)程中沒(méi)有出現(xiàn)沖突的發(fā)生,這很像我們?cè)谥拔恼轮薪榻B的 CAS 實(shí)現(xiàn),CAS 的原理和應(yīng)用就是無(wú)鎖的實(shí)現(xiàn)。無(wú)鎖無(wú)法全面代替有鎖,但無(wú)鎖在某些場(chǎng)合下的性能是非常高的。

          偏向鎖

          HotSpot 的作者經(jīng)過(guò)研究發(fā)現(xiàn),大多數(shù)情況下,鎖不僅不存在多線程競(jìng)爭(zhēng),還存在鎖由同一線程多次獲得的情況,偏向鎖就是在這種情況下出現(xiàn)的,它的出現(xiàn)是為了解決只有在一個(gè)線程執(zhí)行同步時(shí)提高性能。

          f5ab40e61af3c42d3cc80d6582e0cd71.webp

          可以從對(duì)象頭的分配中看到,偏向鎖要比無(wú)鎖多了線程ID?和?epoch,下面我們就來(lái)描述一下偏向鎖的獲取過(guò)程

          偏向鎖獲取過(guò)程

          1. 首先線程訪問(wèn)同步代碼塊,會(huì)通過(guò)檢查對(duì)象頭 Mark Word 的鎖標(biāo)志位判斷目前鎖的狀態(tài),如果是 01,說(shuō)明就是無(wú)鎖或者偏向鎖,然后再根據(jù)是否偏向鎖?的標(biāo)示判斷是無(wú)鎖還是偏向鎖,如果是無(wú)鎖情況下,執(zhí)行下一步

          2. 線程使用 CAS 操作來(lái)嘗試對(duì)對(duì)象加鎖,如果使用 CAS 替換 ThreadID 成功,就說(shuō)明是第一次上鎖,那么當(dāng)前線程就會(huì)獲得對(duì)象的偏向鎖,此時(shí)會(huì)在對(duì)象頭的 Mark Word 中記錄當(dāng)前線程 ID 和獲取鎖的時(shí)間 epoch 等信息,然后執(zhí)行同步代碼塊。

          全局安全點(diǎn)(Safe Point):全局安全點(diǎn)的理解會(huì)涉及到 C 語(yǔ)言底層的一些知識(shí),這里簡(jiǎn)單理解 SafePoint 是 Java 代碼中的一個(gè)線程可能暫停執(zhí)行的位置。

          等到下一次線程在進(jìn)入和退出同步代碼塊時(shí)就不需要進(jìn)行?CAS?操作進(jìn)行加鎖和解鎖,只需要簡(jiǎn)單判斷一下對(duì)象頭的 Mark Word 中是否存儲(chǔ)著指向當(dāng)前線程的線程ID,判斷的標(biāo)志當(dāng)然是根據(jù)鎖的標(biāo)志位來(lái)判斷的。如果用流程圖來(lái)表示的話就是下面這樣

          11cbdfb2ae953a43d5e9f08525f505d6.webp

          關(guān)閉偏向鎖

          偏向鎖在Java 6 和Java 7 里是默認(rèn)啟用的。由于偏向鎖是為了在只有一個(gè)線程執(zhí)行同步塊時(shí)提高性能,如果你確定應(yīng)用程序里所有的鎖通常情況下處于競(jìng)爭(zhēng)狀態(tài),可以通過(guò)JVM參數(shù)關(guān)閉偏向鎖:-XX:-UseBiasedLocking=false,那么程序默認(rèn)會(huì)進(jìn)入輕量級(jí)鎖狀態(tài)。

          關(guān)于 epoch

          偏向鎖的對(duì)象頭中有一個(gè)被稱為?epoch?的值,它作為偏差有效性的時(shí)間戳。

          輕量級(jí)鎖

          輕量級(jí)鎖是指當(dāng)前鎖是偏向鎖的時(shí)候,資源被另外的線程所訪問(wèn),那么偏向鎖就會(huì)升級(jí)為輕量級(jí)鎖,其他線程會(huì)通過(guò)自旋的形式嘗試獲取鎖,不會(huì)阻塞,從而提高性能,下面是詳細(xì)的獲取過(guò)程。

          輕量級(jí)鎖加鎖過(guò)程

          1. 緊接著上一步,如果 CAS 操作替換 ThreadID 沒(méi)有獲取成功,執(zhí)行下一步

          2. 如果使用 CAS 操作替換 ThreadID 失?。ㄟ@時(shí)候就切換到另外一個(gè)線程的角度)說(shuō)明該資源已被同步訪問(wèn)過(guò),這時(shí)候就會(huì)執(zhí)行鎖的撤銷操作,撤銷偏向鎖,然后等原持有偏向鎖的線程到達(dá)全局安全點(diǎn)(SafePoint)時(shí),會(huì)暫停原持有偏向鎖的線程,然后會(huì)檢查原持有偏向鎖的狀態(tài),如果已經(jīng)退出同步,就會(huì)喚醒持有偏向鎖的線程,執(zhí)行下一步

          3. 檢查對(duì)象頭中的 Mark Word 記錄的是否是當(dāng)前線程 ID,如果是,執(zhí)行同步代碼,如果不是,執(zhí)行偏向鎖獲取流程?的第2步。

          如果用流程表示的話就是下面這樣(已經(jīng)包含偏向鎖的獲?。?/p>b24ebc07eeca34f4e8859973c5efafd2.webp

          重量級(jí)鎖

          重量級(jí)鎖其實(shí)就是 synchronized 最終加鎖的過(guò)程,在 JDK 1.6 之前,就是由無(wú)鎖 -> 加鎖的這個(gè)過(guò)程。

          重量級(jí)鎖的獲取流程

          1. 接著上面偏向鎖的獲取過(guò)程,由偏向鎖升級(jí)為輕量級(jí)鎖,執(zhí)行下一步

          2. 會(huì)在原持有偏向鎖的線程的棧中分配鎖記錄,將對(duì)象頭中的 Mark Word 拷貝到原持有偏向鎖線程的記錄中,原持有偏向鎖的線程獲得輕量級(jí)鎖,然后喚醒原持有偏向鎖的線程,從安全點(diǎn)處繼續(xù)執(zhí)行,執(zhí)行完畢后,執(zhí)行下一步,當(dāng)前線程執(zhí)行第 4 步

          3. 執(zhí)行完畢后,開(kāi)始輕量級(jí)解鎖操作,解鎖需要判斷兩個(gè)條件

          • 判斷對(duì)象頭中的 Mark Word 中鎖記錄指針是否指向當(dāng)前棧中記錄的指針

          06beaabd7833792b9377fb20fbbeecc3.webp
          • 拷貝在當(dāng)前線程鎖記錄的 Mark Word 信息是否與對(duì)象頭中的 Mark Word 一致。

          如果上面兩個(gè)判斷條件都符合的話,就進(jìn)行鎖釋放,如果其中一個(gè)條件不符合,就會(huì)釋放鎖,并喚起等待的線程,進(jìn)行新一輪的鎖競(jìng)爭(zhēng)。

          1. 在當(dāng)前線程的棧中分配鎖記錄,拷貝對(duì)象頭中的 MarkWord 到當(dāng)前線程的鎖記錄中,執(zhí)行 CAS 加鎖操作,會(huì)把對(duì)象頭 Mark Word 中鎖記錄指針指向當(dāng)前線程鎖記錄,如果成功,獲取輕量級(jí)鎖,執(zhí)行同步代碼,然后執(zhí)行第3步,如果不成功,執(zhí)行下一步

          2. 當(dāng)前線程沒(méi)有使用 CAS 成功獲取鎖,就會(huì)自旋一會(huì)兒,再次嘗試獲取,如果在多次自旋到達(dá)上限后還沒(méi)有獲取到鎖,那么輕量級(jí)鎖就會(huì)升級(jí)為?重量級(jí)鎖

          981585210fdcf46345b25cbf84c650f0.webp

          如果用流程圖表示是這樣的

          cbc250e1734c190d4ed0cc1066f9ab03.webp

          根據(jù)上面對(duì)于鎖升級(jí)細(xì)致的描述,我們可以總結(jié)一下不同鎖的適用范圍和場(chǎng)景。

          eee6b4514de4fa33dfd523d815fa57c7.webp

          synchronized 代碼塊的底層實(shí)現(xiàn)

          為了便于方便研究,我們把 synchronized 修飾代碼塊的示例簡(jiǎn)單化,如下代碼所示

          public?class?SynchronizedTest?{

          ????private?int?i;

          ????public?void?syncTask(){
          ????????synchronized?(this){
          ????????????i++;
          ????????}
          ????}

          }

          我們主要關(guān)注一下 synchronized 的字節(jié)碼,如下所示

          4ef03a98218423477f9d6e0dec5ba795.webp

          從這段字節(jié)碼中我們可以知道,同步語(yǔ)句塊使用的是 monitorenter 和 monitorexit 指令,其中 monitorenter 指令指向同步代碼塊的開(kāi)始位置,monitorexit 指令指向同步代碼塊的結(jié)束位置。

          那么為什么會(huì)有兩個(gè) monitorexit 呢?

          不知道你注意到下面的異常表了嗎?如果你不知道什么是異常表,那么我建議你讀一下這篇文章

          看完這篇Exception 和 Error,和面試官扯皮就沒(méi)問(wèn)題了

          synchronized 修飾方法的底層原理

          方法的同步是隱式的,也就是說(shuō) synchronized 修飾方法的底層無(wú)需使用字節(jié)碼來(lái)控制,真的是這樣嗎?我們來(lái)反編譯一波看看結(jié)果

          public?class?SynchronizedTest?{

          ????private?int?i;

          ????public?synchronized?void?syncTask(){
          ????????i++;
          ????}
          }

          這次我們使用?javap -verbose?來(lái)輸出詳細(xì)的結(jié)果

          2320cc81cac082fed9eac7bd3dad6f37.webp

          從字節(jié)碼上可以看出,synchronized 修飾的方法并沒(méi)有使用 monitorenter 和 monitorexit 指令,取得代之是ACC_SYNCHRONIZED 標(biāo)識(shí),該標(biāo)識(shí)指明了此方法是一個(gè)同步方法,JVM 通過(guò)該 ACC_SYNCHRONIZED 訪問(wèn)標(biāo)志來(lái)辨別一個(gè)方法是否聲明為同步方法,從而執(zhí)行相應(yīng)的同步調(diào)用。這就是 synchronized 鎖在同步代碼塊上和同步方法上的實(shí)現(xiàn)差別。



          efffaf3f66b08f2e8d6646256bdba8b9.webp


          0、重磅!兩萬(wàn)字長(zhǎng)文總結(jié),梳理 Java 入門(mén)進(jìn)階哪些事(推薦收藏)

          0e6bd129c0e75dca414703e158c5f61c.webp

          瀏覽 61
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          評(píng)論
          圖片
          表情
          推薦
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  久久Y成人电影 | 十大免费黄色网址 | 免费看大学生裸体AV | 日日夜夜拍拍 | 亚洲第一狼区 |