站在JVM角度看Java的鎖

​ 併發是從JDK 5升級到JDK 6後一項重要的改進項,HotSpot虛擬機開發團隊在這個版本上花費了大量的資源去實現各類鎖優化技術,如適應性自旋(Adaptive Spinning)、鎖消除(Lock Elimination)、鎖膨脹(Lock Coarsening)、輕量級鎖(Lightweight Locking)、偏向鎖(Biased Locking)等,這些技術都是爲了在線程之間更高效地共享數據及解決競爭問題,從而提升程序的執行效率 .數組

存在的問題

​ 對於最開始 (JDK1.5以前), Java的同步只能是一個synchronized修飾, 進行同步, 可是這個由很大的問題. 只會有一個線程能夠entermonitor , 而後計數器+1. 稱爲重量級鎖. 其餘線程都被掛起, 咱們知道對於大多數JVM來講, 線程是和操做系統的線程是一一綁定的, 也就是我操做的線程掛起須要由內核來完成, 這時候就須要用戶態轉換到內核態 ,而後內核執行此線程掛起, 當要恢復線程的時候再通知內核, 此時會形成很嚴重的問題 . 咱們知道對於CPU來講, 他是靠時間片來實現的多線程並行執行, 若是我一個同步任務只會好比count++ , 他執行很短, 短到幾ns級別, 而掛起線程和恢復線程的實現遠遠大於幾ns , 可能大幾個量級 .微信

​ 所以聰明的人想到一個事情, 就是我不讓你掛起, 這麼短我就本身空轉一會, 也很短, (空轉的意思其實就是while(true) 啥也不作,可是不是讓CPU掛起,這個也稱之爲自旋) , 咱們知道空轉就是一種浪費CPU的事情 , 可是這個浪費得有個度 , 咱們上訴的問題, 每一個線程可能空轉的時間也就幾ns , 可是對於長到幾秒的還能空轉嗎, 不行了. 因此這裏就是一個劃分點.多線程

​ 還有一個問題就是, 好比某一段時間內, 就一個線程處於運做中, 那麼此時還須要加鎖操做嗎 ? 是否須要優化 .併發

​ 所以引出了下文的解決方案.oop

自旋鎖

​ 自旋鎖是JDK1.4.2的時候引入的, 默認爲關閉狀態, 可使用-XX:+UseSpinning參數來開啓 , 可是這個自旋鎖他不是一直的自旋, 他有個度, 這個度能夠用-XX:PreBlockSpin 來控制自旋多少次, 默認是10次.佈局

自適應自旋

​ JDK 6中對自旋鎖的優化,引入了自適應的自旋。性能

​ **自適應意味着自旋的時間再也不是固定的了,而是由前一次在同一個鎖上的自旋時間及鎖的擁有者的狀態來決定的。**若是在同一個鎖對象上,自旋等待剛剛成功得到過鎖,而且持有鎖的線程正在運行中,那麼虛擬機就會認爲此次自旋也頗有可能再次成功,進而容許自旋等待持續相對更長的時間,好比持續100次忙循環。另外一方面,若是對於某個鎖,自旋不多成功得到過鎖,那在之後要獲取這個鎖時將有可能直接省略掉自旋過程,以免浪費處理器資源。有了自適應自旋,隨着程序運行時間的增加及性能監控信息的不斷完善,虛擬機對程序鎖的情況預測就會愈來愈精準,虛擬機就會變得愈來愈「聰明」了。優化

Java 對象的內存佈局(重要)

​ 瞭解輕量級鎖和偏向鎖 須要瞭解Java對象的內存佈局.操作系統

再看下面以前 , 要了瞭解一個JAVA對象的內存結構 , 也稱之爲對象的內存佈局線程

對象頭 :

一、對象自身的運行時數據( MarkWord )

存儲 hashCode、GC 分代年齡、鎖類型標記、偏向鎖線程 ID 、CAS 鎖指向線程 LockRecord 的指針等,synconized 鎖的機制與這個部分( markwork )密切相關,用 markword 中最低的三位表明鎖的狀態,其中一位是偏向鎖位,另外兩位是普通鎖位。

關於markword , 這個是32位操做系統的實現,

二、對象類型指針( Class Pointer )

​ 對象指向它的類元數據的指針(這個指針相似於C語言的指針, 指針大小是根據操做系統決定的,64位好像是8個字節大小, 由於64位系統的尋址空間很大), JVM 就是經過它來肯定是哪一個 Class 的實例。

​ 若是是數組對象,還會有一個額外的部分用於存儲數組長度。由於虛擬機能夠經過普通Java對象的元數據信息肯定Java對象的大小,可是若是數組的長度是不肯定的,將沒法經過元數據中的信息推斷出數組的大小。也就是arr.len調用很方便.

實例數據區域

此處存儲的是對象真正有效的信息,好比對象中全部字段的內容 . ,不管是從父類繼承下來的,仍是在子類中定義的字段都必須記錄起來。

​ 這部分的存儲順序會受到虛擬機分配策略參數(-XX:FieldsAllocationStyle參數)和字段在Java源碼中定義順序的影響。HotSpot虛擬機默認的分配順序爲longs/doubles、ints、shorts/chars、bytes/booleans、oops(Ordinary Object Pointers,OOPs)(這裏基本能夠肯定Java的類型也就8種),從以上默認的分配策略中能夠看到,相同寬度的字段老是被分配到一塊兒存放,在知足這個前提條件的狀況下,在父類中定義的變量會出如今子類以前。若是HotSpot虛擬機的+XX:CompactFields參數值爲true(默認就爲true),那子類之中較窄的變量也容許插入父類變量的空隙之中,以節省出一點點空間。

對齊填充

​ 對象的第三部分是對齊填充,這並非必然存在的,也沒有特別的含義,它僅僅起着佔位符的做用。因爲HotSpot虛擬機的自動內存管理系統要求對象起始地址必須是8字節的整數倍,換句話說就是任何對象的大小都必須是8字節的整數倍。對象頭部分已經被精心設計成正好是8字節的倍數(1倍或者2倍),所以,若是對象實例數據部分沒有對齊的話,就須要經過對齊填充來補全。

​ 其實也是爲了存儲方便.

​ 若是你仍是對上述不理解的話, 你就看看 <深刻理解Java虛擬機> , 裏面有. 接下來就看看具體內容了 .

synchronized 鎖升級流程

​ synchronized 鎖並非直接進去就是一個重量級鎖, 而是有所思考的, 由於不少短的操做,並不須要掛起線程. 因此相似於空轉 , 還有就是單線程加鎖. 何須掛起線程呢, 因此sync也幫助咱們解決了這個問題.

偏向鎖

​ 在 JDK1.8 中,其實默認是輕量級鎖,但若是設定了-XX:BiasedLockingStartupDelay=0 ,那在對一個 Object 作 syncronized 的時候,會當即上一把偏向鎖。當處於偏向鎖狀態時, markwork 會記錄當前線程 ID。

​ 它的意思是這個鎖會偏向於第一個得到它的線程,若是在接下來的執行過程當中,該鎖一直沒有被其餘的線程獲取,則持有偏向鎖的線程將永遠不須要再進行同步。偏向鎖解決的問題是, 有些時候就一個線程在運行, 難道還有多線程問題嗎, 因此並不須要. 當出現第二個線程去競爭的狀況下才會出現降級 .

原理: 當鎖對象第一次被線程獲取的時候,虛擬機將會把對象頭中的標誌位設置爲「01」、把偏向模式設置爲「1」,表示進入偏向模式。同時使用CAS操做把獲取到這個鎖的線程的ID記錄在對象的Mark Word之中。若是CAS操做成功,持有偏向鎖的線程之後每次進入這個鎖相關的同步塊時,虛擬機均可以再也不進行任何同步操做(例如加鎖、解鎖及對Mark Word的更新操做等)。 一旦出現另一個線程去嘗試獲取這個鎖的狀況,偏向模式就立刻宣告結束。

輕量級鎖

​ 當下一個線程參與到偏向鎖競爭時,會先判斷 markword 中保存的線程 ID 是否與這個線程 ID 相等,若是不相等,會當即撤銷偏向鎖,升級爲輕量級鎖。每一個線程在本身的線程棧中生成一個 LockRecord ( LR ),而後每一個線程經過 CAS (自旋 )的操做將鎖對象頭中的 markwork 設置爲指向本身的 LR 的指針,哪一個線程設置成功,就意味着得到鎖。 關於 synchronized 中此時執行的 CAS 操做是經過 native 的調用 HotSpot 中 bytecodeInterpreter.cpp 文件 C++ 代碼實現的,有興趣的能夠繼續深挖。

重量級鎖

​ 若是鎖競爭加重(如線程自旋次數或者自旋的線程數超過某閾值, JDK1.6 以後,由 JVM 本身控制該規則),就會升級爲重量級鎖。此時就會向操做系統申請資源,線程掛起,進入到操做系統內核態的等待隊列中,等待操做系統調度,而後映射回用戶態。在重量級鎖中,因爲須要作內核態到用戶態的轉換,而這個過程當中須要消耗較多時間,也就是"重"的緣由之一。

可重入

​ synchronized 擁有強制原子性的內部鎖機制,是一把可重入鎖。所以,在一個線程使用 synchronized 方法時調用該對象另外一個 synchronized 方法,即一個線程獲得一個對象鎖後再次請求該對象鎖,是永遠能夠拿到鎖的。在 Java 中線程得到對象鎖的操做是以線程爲單位的,而不是以調用爲單位的。 synchronized 鎖的對象頭的 markwork 中會記錄該鎖的線程持有者和計數器,當一個線程請求成功後, JVM 會記下持有鎖的線程,並將計數器計爲1。此時其餘線程請求該鎖,則必須等待。而該持有鎖的線程若是再次請求這個鎖,就能夠再次拿到這個鎖,同時計數器會遞增。當線程退出一個 synchronized 方法/塊時,計數器會遞減,若是計數器爲 0 則釋放該鎖鎖。

悲觀鎖(互斥鎖、排他鎖)

​ synchronized 是一把悲觀鎖(獨佔鎖),當前線程若是獲取到鎖,會致使其它全部須要鎖該的線程等待,一直等待持有鎖的線程釋放鎖才繼續進行鎖的爭搶。

​ 能夠參考一下 阿里中間件微信公衆號的一篇文章 : mp.weixin.qq.com/s/h3VIUyH9L…

同時以上部分大部分來自於 <<深刻理解Java虛擬機>>

相關文章
相關標籤/搜索