日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當(dāng)前位置: 首頁 > 编程语言 > java >内容正文

java

问懵逼:请站在 JVM 角度谈谈 Java 的锁?

發(fā)布時間:2025/3/21 java 20 豆豆
生活随笔 收集整理的這篇文章主要介紹了 问懵逼:请站在 JVM 角度谈谈 Java 的锁? 小編覺得挺不錯的,現(xiàn)在分享給大家,幫大家做個參考.

并發(fā)是從JDK 5升級到JDK 6后一項重要的改進(jìn)項,HotSpot虛擬機(jī)開發(fā)團(tuán)隊在這個版本上花費了大量的資源去實現(xiàn)各種鎖優(yōu)化技術(shù),如適應(yīng)性自旋(Adaptive Spinning)、鎖消除(Lock Elimination)、鎖膨脹(Lock Coarsening)、輕量級鎖(Lightweight Locking)、偏向鎖(Biased Locking)等,這些技術(shù)都是為了在線程之間更高效地共享數(shù)據(jù)及解決競爭問題,從而提高程序的執(zhí)行效率 .

存在的問題

對于最開始 (JDK1.5之前), Java的同步只能是一個synchronized修飾, 進(jìn)行同步, 但是這個由很大的問題. 只會有一個線程可以entermonitor , 然后計數(shù)器+1. 稱為重量級鎖. 其他線程都被掛起, 我們知道對于大多數(shù)JVM來說, 線程是和操作系統(tǒng)的線程是一一綁定的, 也就是我操作的線程掛起需要由內(nèi)核來完成, 這時候就需要用戶態(tài)轉(zhuǎn)換到內(nèi)核態(tài) ,然后內(nèi)核執(zhí)行此線程掛起, 當(dāng)要恢復(fù)線程的時候再通知內(nèi)核, ?此時會造成很嚴(yán)重的問題 . 我們知道對于CPU來說, 他是靠時間片來實現(xiàn)的多線程并行執(zhí)行, 如果我一個同步任務(wù)只會比如count++ , 他執(zhí)行很短, 短到幾ns級別, 而掛起線程和恢復(fù)線程的實現(xiàn)遠(yuǎn)遠(yuǎn)大于幾ns?, 可能大幾個量級 .

因此聰明的人想到一個事情, 就是我不讓你掛起, 這么短我就自己空轉(zhuǎn)一會, 也很短, (空轉(zhuǎn)的意思其實就是while(true) 啥也不做,但是不是讓CPU掛起,這個也稱之為自旋) , 我們知道空轉(zhuǎn)就是一種浪費CPU的事情 , 但是這個浪費得有個度 , 我們上訴的問題, 每個線程可能空轉(zhuǎn)的時間也就幾ns , 但是對于長到幾秒的還能空轉(zhuǎn)嗎, 不行了. 所以這里就是一個劃分點.

還有一個問題就是, 比如某一段時間內(nèi), 就一個線程處于運作中, 那么此時還需要加鎖操作嗎 ? 是否需要優(yōu)化 .

因此引出了下文的解決方案.

自旋鎖

自旋鎖是JDK1.4.2的時候引入的, 默認(rèn)為關(guān)閉狀態(tài), 可以使用-XX:+UseSpinning參數(shù)來開啟 , 但是這個自旋鎖他不是一直的自旋, 他有個度, 這個度可以用-XX:PreBlockSpin?來控制自旋多少次, 默認(rèn)是10次.

自適應(yīng)自旋

JDK 6中對自旋鎖的優(yōu)化,引入了自適應(yīng)的自旋。

**自適應(yīng)意味著自旋的時間不再是固定的了,而是由前一次在同一個鎖上的自旋時間及鎖的擁有者的狀態(tài)來決定的。**如果在同一個鎖對象上,自旋等待剛剛成功獲得過鎖,并且持有鎖的線程正在運行中,那么虛擬機(jī)就會認(rèn)為這次自旋也很有可能再次成功,進(jìn)而允許自旋等待持續(xù)相對更長的時間,比如持續(xù)100次忙循環(huán)。另一方面,如果對于某個鎖,自旋很少成功獲得過鎖,那在以后要獲取這個鎖時將有可能直接省略掉自旋過程,以避免浪費處理器資源。有了自適應(yīng)自旋,隨著程序運行時間的增長及性能監(jiān)控信息的不斷完善,虛擬機(jī)對程序鎖的狀況預(yù)測就會越來越精準(zhǔn),虛擬機(jī)就會變得越來越“聰明”了。

Java 對象的內(nèi)存布局(重要)

了解輕量級鎖和偏向鎖 需要了解Java對象的內(nèi)存布局.

再看下面之前 , 要了了解一個JAVA對象的內(nèi)存結(jié)構(gòu) , 也稱之為對象的內(nèi)存布局

對象頭 :

1、對象自身的運行時數(shù)據(jù)( MarkWord )

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

關(guān)于markword , ?這個是32位操作系統(tǒng)的實現(xiàn),

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

對象指向它的類元數(shù)據(jù)的指針(這個指針類似于C語言的指針, 指針大小是根據(jù)操作系統(tǒng)決定的,64位好像是8個字節(jié)大小, 因為64位系統(tǒng)的尋址空間很大), JVM 就是通過它來確定是哪個 Class 的實例。

如果是數(shù)組對象,還會有一個額外的部分用于存儲數(shù)組長度。因為虛擬機(jī)可以通過普通Java對象的元數(shù)據(jù)信息確定Java對象的大小,但是如果數(shù)組的長度是不確定的,將無法通過元數(shù)據(jù)中的信息推斷出數(shù)組的大小。也就是arr.len調(diào)用很方便.

實例數(shù)據(jù)區(qū)域

此處存儲的是對象真正有效的信息,比如對象中所有字段的內(nèi)容?. ,無論是從父類繼承下來的,還是在子類中定義的字段都必須記錄起來。

這部分的存儲順序會受到虛擬機(jī)分配策略參數(shù)(-XX:FieldsAllocationStyle參數(shù))和字段在Java源碼中定義順序的影響。HotSpot虛擬機(jī)默認(rèn)的分配順序為longs/doubles、ints、shorts/chars、bytes/booleans、oops(Ordinary Object Pointers,OOPs)(這里基本可以確定Java的類型也就8種),從以上默認(rèn)的分配策略中可以看到,相同寬度的字段總是被分配到一起存放,在滿足這個前提條件的情況下,在父類中定義的變量會出現(xiàn)在子類之前。如果HotSpot虛擬機(jī)的+XX:CompactFields參數(shù)值為true(默認(rèn)就為true),那子類之中較窄的變量也允許插入父類變量的空隙之中,以節(jié)省出一點點空間。

對齊填充

對象的第三部分是對齊填充,這并不是必然存在的,也沒有特別的含義,它僅僅起著占位符的作用。由于HotSpot虛擬機(jī)的自動內(nèi)存管理系統(tǒng)要求對象起始地址必須是8字節(jié)的整數(shù)倍,換句話說就是任何對象的大小都必須是8字節(jié)的整數(shù)倍。對象頭部分已經(jīng)被精心設(shè)計成正好是8字節(jié)的倍數(shù)(1倍或者2倍),因此,如果對象實例數(shù)據(jù)部分沒有對齊的話,就需要通過對齊填充來補(bǔ)全。

其實也是為了存儲方便.

如果你還是對上述不理解的話, 你就看看 <深入理解Java虛擬機(jī)> , 里面有. 接下來就看看具體內(nèi)容了 .

synchronized 鎖升級流程

synchronized 鎖并不是直接進(jìn)去就是一個重量級鎖, 而是有所思考的, 因為很多短的操作,并不需要掛起線程. 所以類似于空轉(zhuǎn) , 還有就是單線程加鎖. 何必掛起線程呢, 所以sync也幫助我們解決了這個問題.

偏向鎖

在 JDK1.8 中,其實默認(rèn)是輕量級鎖,但如果設(shè)定了-XX:BiasedLockingStartupDelay=0?,那在對一個 Object 做 syncronized 的時候,會立即上一把偏向鎖。當(dāng)處于偏向鎖狀態(tài)時, markwork 會記錄當(dāng)前線程 ID。

它的意思是這個鎖會偏向于第一個獲得它的線程,如果在接下來的執(zhí)行過程中,該鎖一直沒有被其他的線程獲取,則持有偏向鎖的線程將永遠(yuǎn)不需要再進(jìn)行同步。偏向鎖解決的問題是,?有些時候就一個線程在運行, 難道還有多線程問題嗎, 所以并不需要. 當(dāng)出現(xiàn)第二個線程去競爭的情況下才會出現(xiàn)降級?.

原理:??當(dāng)鎖對象第一次被線程獲取的時候,虛擬機(jī)將會把對象頭中的標(biāo)志位設(shè)置為“01”、把偏向模式設(shè)置為“1”,表示進(jìn)入偏向模式。同時使用CAS操作把獲取到這個鎖的線程的ID記錄在對象的Mark Word之中。如果CAS操作成功,持有偏向鎖的線程以后每次進(jìn)入這個鎖相關(guān)的同步塊時,虛擬機(jī)都可以不再進(jìn)行任何同步操作(例如加鎖、解鎖及對Mark Word的更新操作等)。一旦出現(xiàn)另外一個線程去嘗試獲取這個鎖的情況,偏向模式就馬上宣告結(jié)束。

輕量級鎖

當(dāng)下一個線程參與到偏向鎖競爭時,會先判斷 markword 中保存的線程 ID 是否與這個線程 ID 相等,如果不相等,會立即撤銷偏向鎖,升級為輕量級鎖。每個線程在自己的線程棧中生成一個 LockRecord ( LR ),然后每個線程通過 CAS (自旋 )的操作將鎖對象頭中的 markwork 設(shè)置為指向自己的 LR 的指針,哪個線程設(shè)置成功,就意味著獲得鎖。?關(guān)于 synchronized 中此時執(zhí)行的 CAS 操作是通過 native 的調(diào)用 HotSpot 中 bytecodeInterpreter.cpp 文件 C++ 代碼實現(xiàn)的,有興趣的可以繼續(xù)深挖。

重量級鎖

如果鎖競爭加劇(如線程自旋次數(shù)或者自旋的線程數(shù)超過某閾值, JDK1.6 之后,由 JVM 自己控制該規(guī)則),就會升級為重量級鎖。此時就會向操作系統(tǒng)申請資源,線程掛起,進(jìn)入到操作系統(tǒng)內(nèi)核態(tài)的等待隊列中,等待操作系統(tǒng)調(diào)度,然后映射回用戶態(tài)。在重量級鎖中,由于需要做內(nèi)核態(tài)到用戶態(tài)的轉(zhuǎn)換,而這個過程中需要消耗較多時間,也就是"重"的原因之一。

可重入

synchronized 擁有強(qiáng)制原子性的內(nèi)部鎖機(jī)制,是一把可重入鎖。因此,在一個線程使用 synchronized 方法時調(diào)用該對象另一個 synchronized 方法,即一個線程得到一個對象鎖后再次請求該對象鎖,是永遠(yuǎn)可以拿到鎖的。在 Java 中線程獲得對象鎖的操作是以線程為單位的,而不是以調(diào)用為單位的。synchronized 鎖的對象頭的 markwork 中會記錄該鎖的線程持有者和計數(shù)器,當(dāng)一個線程請求成功后, JVM 會記下持有鎖的線程,并將計數(shù)器計為1。此時其他線程請求該鎖,則必須等待。而該持有鎖的線程如果再次請求這個鎖,就可以再次拿到這個鎖,同時計數(shù)器會遞增。當(dāng)線程退出一個 ?synchronized 方法/塊時,計數(shù)器會遞減,如果計數(shù)器為 0 則釋放該鎖鎖。

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

synchronized 是一把悲觀鎖(獨占鎖),當(dāng)前線程如果獲取到鎖,會導(dǎo)致其它所有需要鎖該的線程等待,一直等待持有鎖的線程釋放鎖才繼續(xù)進(jìn)行鎖的爭搶。

總結(jié)

以上是生活随笔為你收集整理的问懵逼:请站在 JVM 角度谈谈 Java 的锁?的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網(wǎng)站內(nèi)容還不錯,歡迎將生活随笔推薦給好友。