自拍偷在线精品自拍偷,亚洲欧美中文日韩v在线观看不卡

Synchronized 的超多干貨!一起來(lái)品品!

開(kāi)發(fā) 后端
本篇文章就帶你從 synchronized 的基本用法、再到 synchronized 的深入理解,對(duì)象頭等,為你揭開(kāi) synchronized 的面紗。

[[403316]]

synchronized 這個(gè)關(guān)鍵字的重要性不言而喻,幾乎可以說(shuō)是并發(fā)、多線程必須會(huì)問(wèn)到的關(guān)鍵字了。synchronized 會(huì)涉及到鎖、升級(jí)降級(jí)操作、鎖的撤銷(xiāo)、對(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ì)類(lèi)的實(shí)例進(jìn)行加鎖,進(jìn)入同步代碼前需要獲得當(dāng)前實(shí)例的鎖
  • synchronized 修飾靜態(tài)方法,相當(dāng)于是對(duì)類(lèi)對(duì)象進(jìn)行加鎖
  • synchronized 修飾代碼塊,相當(dāng)于是給對(duì)象進(jìn)行加鎖,在進(jìn)入代碼塊前需要先獲得對(duì)象的鎖

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

synchronized 修飾實(shí)例方法

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

  1. public synchronized void method() 
  2.    // ... 

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

  1. public class TSynchronized implements Runnable{ 
  2.  
  3.     static int i = 0; 
  4.  
  5.     public synchronized void increase(){ 
  6.         i++; 
  7.         System.out.println(Thread.currentThread().getName()); 
  8.     } 
  9.  
  10.  
  11.     @Override 
  12.     public void run() { 
  13.         for(int i = 0;i < 1000;i++) { 
  14.             increase(); 
  15.         } 
  16.     } 
  17.  
  18.     public static void main(String[] args) throws InterruptedException { 
  19.  
  20.         TSynchronized tSynchronized = new TSynchronized(); 
  21.         Thread aThread = new Thread(tSynchronized); 
  22.         Thread bThread = new Thread(tSynchronized); 
  23.         aThread.start(); 
  24.         bThread.start(); 
  25.         aThread.join(); 
  26.         bThread.join(); 
  27.         System.out.println("i = " + i); 
  28.     } 

上面輸出的結(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 類(lèi)的同時(shí)都會(huì)創(chuàng)建一個(gè) increase 方法,increase 方法中只是打印出來(lái)了當(dāng)前訪問(wèn)的線程名稱。Synchronized 類(lèi)實(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)鍵字一起使用

  1. public static synchronized void increase(){} 

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

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

synchronized 修飾代碼塊

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

  1. public void run() { 
  2.   synchronized(obj){ 
  3.     for(int j = 0;j < 1000;j++){ 
  4.       i++; 
  5.     } 
  6.   } 

上面代碼中將 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)行鎖定

  1. // 實(shí)例對(duì)象鎖 
  2. synchronized(this){ 
  3.     for(int j = 0;j < 1000;j++){ 
  4.         i++; 
  5.     } 
  6.  
  7. //class對(duì)象鎖 
  8. synchronized(TSynchronized.class){ 
  9.     for(int j = 0;j < 1000;j++){ 
  10.         i++; 
  11.     } 

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)存分布如下圖所示

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

對(duì)象頭 Header

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

在 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é)具體是如何分配的。

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

  • 無(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類(lèi)中的枚舉窺出端倪

來(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)銷(xiāo)也沒(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)的)

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

_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 的值。

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

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

實(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ō)

無(wú)鎖

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

無(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í)提高性能。

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

偏向鎖獲取過(guò)程

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

線程使用 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)表示的話就是下面這樣

關(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í)行鎖的撤銷(xiāo)操作,撤銷(xiāo)偏向鎖,然后等原持有偏向鎖的線程到達(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)包含偏向鎖的獲取)

重量級(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)前棧中記錄的指針

  • 拷貝在當(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í)鎖

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

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

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

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

  1. public class SynchronizedTest { 
  2.  
  3.     private int i; 
  4.  
  5.     public void syncTask(){ 
  6.         synchronized (this){ 
  7.             i++; 
  8.         } 
  9.     } 
  10.  

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

從這段字節(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é)果

  1. public class SynchronizedTest { 
  2.  
  3.     private int i; 
  4.  
  5.     public synchronized void syncTask(){ 
  6.         i++; 
  7.     } 

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

從字節(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)差別。

本文轉(zhuǎn)載自微信公眾號(hào)「程序員cxuan」,可以通過(guò)以下二維碼關(guān)注。轉(zhuǎn)載本文請(qǐng)聯(lián)系程序員cxuan公眾號(hào)。

 

責(zé)任編輯:武曉燕 來(lái)源: 程序員cxuan
相關(guān)推薦

2012-04-14 20:47:45

Android

2012-06-25 09:37:24

Web

2024-09-27 08:57:36

2020-06-11 18:35:23

C++編程語(yǔ)言

2012-11-08 17:33:53

智慧云

2009-10-29 16:32:34

Oracle表空間

2021-06-09 08:15:50

volatileJava開(kāi)發(fā)

2010-05-21 17:32:07

IIS服務(wù)器

2012-09-10 13:42:55

PHP項(xiàng)目管理

2021-04-26 11:18:15

FedoraLinuxBug

2011-09-07 22:59:07

聯(lián)想一體機(jī)

2022-05-20 12:14:50

ZuulSpringClou

2010-05-10 15:31:35

Unix文件

2009-07-14 16:35:57

Swing組件大全

2024-09-03 08:09:00

2024-10-17 08:18:52

2021-12-13 07:28:34

Linux驅(qū)動(dòng)中斷

2012-07-10 09:14:51

Web

2017-11-02 15:28:52

點(diǎn)贊
收藏

51CTO技術(shù)棧公眾號(hào)