「轉載」java架構之路(多線程)synchronized詳解以及鎖的膨脹升級...

2020-12-09 做一個編程俗人

上幾次博客,我們把volatile基本都說完了,剩下的還有我們的synchronized,還有我們的AQS,這次博客我來說一下synchronized的使用和原理。

synchronized是jvm內部的一把隱式鎖,一切的加鎖和解鎖過程是由jvm虛擬機來控制的,不需要我們認為的幹預,我們大致從了解鎖,到synchronized的使用,到鎖的膨脹升級過程三個角度來說一下synchronized。

鎖的分類

java中我們聽到很多的鎖,什麼顯示鎖,隱式鎖,公平鎖,重入鎖等等,下面我來總結一張圖來供大家學習使用。

這次博客我們主要來說我們的隱示鎖,就是我們的無鎖到重量級鎖。

synchronized的使用

我們先來看一段簡單的代碼

publicclass SynchronizedTest { privatestatic Object object = new Object(); publicstaticvoid main(String[] args) { synchronized (object){ System.out.println("只有我拿到鎖啦"); } }}

就這樣synchronized就可以使用了,這樣是每次去拿全局對象的object去鎖住後續的代碼段。我們來看一下彙編指令碼

public static void main(java.lang.String[]); Code: 0: getstatic #2 // Field object:Ljava/lang/Object; 3: dup 4: astore_1 5: monitorenter 6: getstatic #3 // Field java/lang/System.out:Ljava/io/PrintStream; 9: ldc #4 // String 只有我拿到鎖啦 11: invokevirtual #5 // Method java/io/PrintStream.println:(Ljava/lang/String;)V 14: aload_1 15: monitorexit 16: goto 2419: astore_2 20: aload_1 21: monitorexit 22: aload_2 23: athrow 24: return Exception table: from to target type61619 any 192219 any

明顯看到了兩個很重要的方法monitorenter和monitorexit兩個方法,也就是說我們的synchronized方法加鎖是基於monitorenter加鎖和monitorexit解鎖來操作的

我們得知是由monitorenter來控制加鎖和monitorexit解鎖的,我們完全可以這樣來操作。上次我們說過一個unsafe類。

publicclass SynchronizedTest { privatestatic Object obj = new Object(); publicvoid lockMethod(){ UnsafeInstance.reflectGetUnsafe().monitorEnter(obj); } publicvoid unLockMethod(){ UnsafeInstance.reflectGetUnsafe().monitorExit(obj); }}

就是我們上次說的unsafe那個類給我們提供了加鎖和解鎖的方法,這樣就是實現誇方法的加鎖和解鎖了,但是超級不建議這樣的使用,後面的AQS回去說別的方式。越過虛擬機直接操作底層的,我們一般是不建議這樣來做的。

我們還可以將synchronized鎖放置在方法上。例如

publicclass SynchronizedTest { privatestatic Object object = new Object(); publicstaticsynchronizedvoid lockMethod() { System.out.println("只有我拿到鎖啦"); }}

這樣加鎖是加在了this當前類對象上的。如果不加static,鎖是加在類對象上的,需要注意我們用的spring的bean作用域

並且我們的synchronized是一個可重入鎖,在jvm源碼中有一個數值來記錄加鎖和解鎖的次數,所以我們是可以多次套用synchronized的

publicvoid lockMethod(){ synchronized(obj){ synchronized(obj){ System.out.println("我沒報錯"); } }}

synchronized到底鎖了什麼

還是拿上個每次加鎖的時候會在對象頭內記錄我們的加鎖信息,我們這裡來說一下對象頭裡面都放置了什麼吧。

以32位JVM內部存儲結構為例

由此看出對象一直是有一個位置來記錄我們的鎖信息的。說到這我們就可以來看一下我們鎖的膨脹升級過程了。

鎖的膨脹升級

我們說過了對象頭的內容,接下來可以說說我們的鎖內部是如何升級上鎖的了。從無鎖到重量級鎖的一個升級過程,我們來邊畫圖,邊詳細看一下。

無鎖狀態:

開始時應該這樣的,線程A和線程B要去爭搶鎖對象,但還未開始爭搶,鎖對象的對象頭是無鎖的狀態也就是25bit位存的hashCode,4bit位存的對象的分代年齡,1bit位記錄是否為偏向鎖,2bit位記錄狀態,優先看最後2bit位,是01,所以說我們的對象可能無鎖或者偏向鎖狀態的,繼續前移一個位置,有1bit專門記錄是否為偏向鎖的,1代表是偏向鎖,0代表無鎖,剛剛開始的時候一定是一個無鎖的狀態,這個不需要多做解釋,系統不同內部bit位存的東西可能有略微差異,但關鍵信息是一致的。

偏向鎖:

這時線程開始佔有鎖對象,比如線程A得到了鎖對象。

就會變成這樣的,線程A拿到鎖對象,將我們的偏向鎖標誌位改為1,並且將原有的hashCode的位置變為23bit位存放線程A的線程ID(用CAS算法得到的線程A的ID),2bit位存epoch,偏向鎖是永遠不會被釋放的。

接下來,線程B也開始運行,線程B也希望得到這把鎖啊,於是線程B會檢查23bit位存的是不是自己的線程ID,因為被線程A已經持有了,一定鎖的23bit位一定不是線程B的線程ID了

然後線程B也會不甘示弱啊,會嘗試修改一次23bit位的對象頭存儲,如果說這時恰好線程A釋放了鎖,可以修改成功,然後線程B就可以持有該偏向鎖了。如果修改失敗,開始升級鎖。自己無法修改,線程B只能找「大哥」了,線程B會通知虛擬機撤銷偏向鎖,然後虛擬機會撤銷偏向鎖,並告知線程A到達安全點進行等待。線程A到達了安全點,會再次判斷線程是否已經退出了同步塊,如果退出了,將23bit位置空,這時鎖不需要升級,線程B可以直接進行使用了,還是將23bit的null改為線程B的線程ID就可以了。

輕量級鎖:如果線程B沒有拿到鎖,我們就會升級到輕量級鎖,首先會在線程A和線程B都開闢一塊LockRecord空間,然後把鎖對象複製一份到自己的LockRecord空間下,並且開闢一塊owner空間留作執行鎖使用,並且鎖對象的前30bit位合併,等待線程A和線程B來修改指向自己的線程,假如線程A修改成功,則鎖對象頭的前30bit位會存線程A的LockRecord的內存地址,並且線程A的owner也會存一份鎖對象的內存地址,形成一個雙向指向的形式。而線程B修改失敗,則進入一個自旋狀態,就是持續來修改鎖對象。

重量級鎖:如果說線程B多次自旋以後還是遲遲沒有拿到鎖,他會繼續上告,告知虛擬機,我多次自旋還是沒有拿到鎖,這時我們的線程B會由用戶態切換到內核態,申請一個互斥量,並且將鎖對象的前30bit指向我們的互斥量地址,並且進入睡眠狀態,然後我們的線程A繼續運行知道完成時,當線程A想要釋放鎖資源時,發現原來鎖的前30bit位並不是指向自己了,這時線程A釋放鎖,並且去喚醒那些處於睡眠狀態的線程,鎖升級到重量級鎖。

逃逸分析

很簡單的一個問題,實例對象存在哪裡?到底是堆還是棧?問題我先不回答,我們先看一段代碼。

publicclass Test { publicstaticvoid main(String[] args) throws InterruptedException { System.out.println("開始"); for (int i = 0; i ) { createCar(); } System.out.println("結束"); Thread.sleep(10000000); } privatestaticvoid createCar() { Car car = new Car(); }}

就是我們運行一個創建對象的方法,一次性創建50萬個Car對象,然後我們讓我們的線程進行深度的睡眠,兩個列印是為了知道我們的對象已經開始創建了和已經創建完成了。我們來運行一下。

然後運行jmap -histo命令來查看我們的線程

我們可以看到,car對象並沒有產生50萬個,別說會被GC掉對象,在運行之前我已經加了GC日誌的參數-XX:+PrintGCDetails,控制臺沒有列印任何GC日誌的。那麼為什麼會這樣呢?我們來看一下我們的代碼,由createCar代碼創建了car對象,但car對象並沒有被其它的方法或者線程去調用,虛擬機會認為你這對象可能只是一個實例化,並沒有進行使用,這時虛擬機會給予你一個優化,就是對於可能沒有使用的對象進行一次逃逸,也就是我們說到的逃逸分析。我們加入 -XX:DoEscapeAnalysis參數再看一次。

這也就是關閉了我們的逃逸分析,虛擬機就會真的為我們創建了50萬個對象。也就是說開啟了逃逸分析有一部分對象只是創建了線程棧上,當線程棧結束,對象也被銷毀,上面的問題也就有答案了,實例對象可能存在堆上,也可能存在棧上。

本文轉載自:https://www.cnblogs.com/cxiaocai/p/12189848.html

相關焦點

  • 「從入門到放棄-Java」並發編程-鎖-synchronized
    簡介上篇【從入門到放棄-Java】並發編程-線程安全中,我們了解到,可以通過加鎖機制來保護共享對象,來實現線程安全。synchronized是java提供的一種內置的鎖機制。通過synchronized關鍵字同步代碼塊。
  • Java多線程synchronized
    本篇主要介紹Java多線程中的同步,也就是如何在Java語言中寫出線程安全的程序,如何在Java語言中解決非線程安全的相關問題。
  • Java synchronized 詳解
    前言記得剛剛學習Java的時候,遇到多線程情況首先想到的就是synchronized。相對於當時的我們來說,synchronized是那麼的神奇而強大,同時它也成為我們解決多線程情況百試不爽的良藥。但是,隨著我們學習的進行,我們知道synchronized是一個重量級的鎖,相對於java.util.concurrent.locks包中提供的各種鎖來說,它顯得那麼的笨重,以至於我們認為它不是那麼的高效而慢慢摒棄它。為了減少重量級鎖的使用,JDK1.6以後,對synchronized相關內容做了一些優化,引入了「偏向鎖」、「輕量級鎖」等內容。
  • Java並發編程:synchronized關鍵字的實現原理——鎖膨脹過程
    前言上一篇分析了優化後的synchronized在不同場景下對象頭中的表現形式,還記得那個結論嗎?當一個線程第一次獲取鎖後再去拿鎖就是偏向鎖,如果有別的線程和當前線程交替執行就膨脹為輕量級鎖,如果發生競爭就會膨脹為重量級鎖。
  • Java的synchronized 能防止指令重排序嗎?
    「二胖:」 這不就是要考我 synchronized 和volatile 這個我擅長啊,我特意背過的,synchronized 是java提供的一個關鍵字它主要能保證原子性、有序性它的底層主要是通過Monitor來實現的。volatile也是java的一個關鍵字它的主要作用是可以保證可見性。。。。此處省略1000字。
  • 大話Synchronized及鎖升級
    }}其中第一種和第三種對等,第二種和第四種對等,這個很簡單,下面是使用 synchronized的總結:鎖升級好,本文的高潮來了,大家仔細聽,在JDK的早期,synchronized叫做重量級鎖,因為申請鎖資源必須通過kernel,系統調用,從用戶態 -> 內核態的轉換,效率比較低,JDK1.6 之後做了一些優化,為了減少獲得鎖和釋放鎖帶來的性能開銷
  • 詳解鎖原理,synchronized、volatile+cas底層實現
    隨著多進程多線程的出現,對共享資源(設備,數據等)的競爭往往會導致資源的使用表現為隨機無序例如:一個線程想在控制臺輸出"I am fine",剛寫到"I am",就被另一線程搶佔控制臺輸出"naughty",導致結果是"I am naughty";對於資源的被搶佔使用,我們能怎麼辦呢?
  • 使用Lock鎖:java多線程安全問題解決方案之Lock鎖
    今天我們來學習一下Lock鎖,它是java 1.5之後出現的接口 java.util.concurrent.locks.Lock接口Lock實現提供了比使用 synchronized 方法和語句可獲得的更廣泛的鎖定操作,就來釋放鎖和獲取鎖來說,在使用synchronized 方法的時候,我們並不知道什麼時候獲取到了鎖,什麼時候釋放了鎖,而Lock接口不一樣,他提供了專門的獲取鎖和釋放鎖的方法。
  • JAVA多線程 集合同步
    主線程啟動thread-2,thread-2得到synchronizedArrayList對象的鎖,接著thread-2得到synchronizedArrayList的迭代器,然後thread-2進入while循環,接著thread-2進入sleep(100)。3.
  • Java之戳中痛點之 synchronized 深度解析
    2、☞ 《Java面試手冊》.PDF    點擊查看作者:Json_wangqiangcnblogs.com/JsonShare/p/11433302.html概覽:簡介:作用、地位、不控制並發的影響用法:對象鎖和類鎖多線程訪問同步方法的7種情況性質:可重入、不可中斷
  • Java多線程:由淺入深看synchronized的底層實現原理
    我會從常見的synchronized加鎖方式入手;引出Java對象在內存的布局,以及鎖的存放位置;然後看一看鎖在C++中的簡單實現思路;最後咱們從字節碼中,看一下JVM如果識別synchronized。內容不是很難,不會涉及到特別多深奧的內容,大部分是平鋪直敘的介紹,很適合閱讀呦~小A:快點開始吧,我等不及啦。
  • JAVA中synchronized與static synchronized 的區別
    是對類的當前實例進行加鎖,防止其他線程同時訪問該類的該實例的所有synchronized塊,注意這裡是「類的當前實例」, 類的兩個不同實例就沒有這種約束了。          一個日本作者-結成浩的《java多線程設計模式》有這樣的一個列子: pulbic class Something(){     publicsynchronizedvoid isSyncA(){}     publicsynchronizedvoid isSyncB(){}     publicstaticsynchronizedvoid
  • Java之戳中痛點之 synchronized 深度解析|java|override|author|...
    多線程訪問同步方法的7種情況    性質:可重入、不可中斷    原理:加解鎖原理、可重入原理、可見性原理    缺陷:效率低、不夠靈活、無法預判是否成功獲取到鎖    如何選擇Lock或Synchronized    如何提高性能
  • Synchronized 的 8 種用法,真是絕了!
    簡介本文將介紹8種同步方法的訪問場景,我們來看看這8種情況下,多線程訪問同步方法是否還是線程安全的。這些場景是多線程編程中經常遇到的,而且也是面試時高頻被問到的問題,所以不管是理論還是實踐,這些都是多線程場景必須要掌握的場景。
  • 源碼時代java小課堂之線程鎖之自旋鎖
    java中定義了非常多的鎖,很多同學面試對於鎖,感覺非常茫然,於是源碼的老師決定,將這些鎖拆分開來注意分析講解,這篇我們先聊聊自旋鎖1.自旋鎖是基於CAS實現2. synchronized重量級鎖是基於系統內核3.
  • Java面試熱點:深入學習並發編程中的synchronized(後三章)
    5. synchronized能鎖住方法和代碼塊,而Lock只能鎖住代碼塊。6. Lock可以使用讀鎖提高多線程讀效率。7. synchronized是非公平鎖,ReentrantLock可以控制是否是公平鎖。
  • Java面試題-多線程篇十三
    線程是作業系統能夠進行運算調度的最小單位,它被包含在進程之中,是進程中的實際運作單位。程式設計師可以通過它進行多處理器編程,你可以使用多線程對運算密集型任務提速。比如,如果一個線程完成一個任務要100毫秒,那麼用十個線程完成改任務只需10毫秒。122,線程和進程有什麼區別?
  • Java鎖的那些事兒
    作者:駱向南部門:有贊教育Java多線程開發中,如果涉及到共享資源操作場景,那就必不可少要和Java鎖打交道。Java中的鎖機制主要分為 Lock和 Synchronized,本文主要分析Java鎖機制的使用和實現原理,按照Java鎖使用、JDK中鎖實現、系統層鎖實現的順序來進行分析,話不多說,let's go~一、Java鎖使用在Lock接口出現之前,Java程序是靠synchronized關鍵字實現鎖功能的,而JavaSE 5之後,並發包中新增了Lock接口(以及相關實現類)
  • 掌握這 8 個Synchronized 用法,你就厲害了!
    這些場景是多線程編程中經常遇到的,而且也是面試時高頻被問到的問題,所以不管是理論還是實踐,這些都是多線程場景必須要掌握的場景。8個場景接下來,我們來通過代碼實現,分別判斷以下場景是不是線程安全的,以及原因是什麼。
  • JAVA 並發編程Synchronized鎖的是什麼?
    這就用到我們的鎖了,當進程想要訪問一個臨界區時,它先會去看看是否已經有其他線程進入了,也就是看是否能獲得鎖。如果沒有其他線程進入,那麼它就進入臨界區,其他線程就無法進入,相當於加鎖。反之,則會被掛起,處於等待狀態,直到其他線程離開臨界區,且本線程被JVM選中才可進入(因為可能有其他線程也在等待)。