鎖(二)

Synchronized 與 鎖原理


22.png

一個對象在 JVM 中的布局可以分為三個區(qū)域姚淆,分別是 對象頭陵叽,實例數(shù)據(jù)媳友,對齊填充。
實例數(shù)據(jù):存放類的屬性數(shù)據(jù)信息撒轮,包括父類的屬性數(shù)據(jù)信息。
對齊填充:由于虛擬機要求贼穆,對象起始地址必須是 8 字節(jié)的整數(shù)倍题山,填充數(shù)據(jù)不是必須存在的,僅僅是為了字節(jié)對齊故痊。
對象頭:Java 對象頭一般占有 2 個機器碼(在32位虛擬機中顶瞳,1個機器碼等于4字節(jié)(32 bit),在64位虛擬機中愕秫,1個機器碼8個字節(jié)(64 bit),但是慨菱,如果是數(shù)組類型,則需要3個機器碼戴甩,因為 JVM 虛擬機可以通過Java對象的元數(shù)據(jù)信息確定Java對象的大小符喝,但是無法從數(shù)據(jù)的元數(shù)據(jù)來確認數(shù)組的大小,所以用一塊來記錄數(shù)組長度甜孤。)

鎖的本質(zhì)實現(xiàn)就是 : 當一個線程獲取到鎖就是把它的線程 id 寫入鎖對象的對象頭 , 來判斷唯一协饲。

對象頭主要包括兩部分數(shù)據(jù):Mark Word 標記字段畏腕,Class Pointer 指針類型。

其中 Class Pointer 是對象指向它的類元數(shù)據(jù)的指針茉稠,虛擬機通過這個指針來確定這個對象是哪個類的對象描馅,
Mark Word 用于存儲對象自身的運行時數(shù)據(jù),它是實現(xiàn)輕量級鎖和偏向鎖的關(guān)鍵而线。

對象頭中 Mark Word 與線程中 Lock Record

Mark Word用于存儲對象自身的運行時數(shù)據(jù)铭污,如:哈希碼(HashCode)、GC分代年齡膀篮、鎖狀態(tài)標志况凉、線程持有的鎖、偏向線程 ID各拷、偏向時間戳等刁绒。

在線程進入同步代碼塊的時候,如果此同步對象沒有被鎖定烤黍,即他的鎖標記位是 01知市,則虛擬機首先會在當前線程的棧中創(chuàng)建我們稱之為 “鎖記錄(Lock Record)”的空間,用于存儲對象的 Mark Word 的拷貝速蕊。

Lock Record 是線程私有的數(shù)據(jù)結(jié)構(gòu)嫂丙,每一個線程都有一個可用的 Lock Record 列表,同時還有哦一個全局的可用列表规哲。每一個被鎖住的對象 Mark Word 都哦會和一個 Lock Record 關(guān)聯(lián)(對象頭的 Mark Word 中的 Lock Word 指向 Lock Record 的起始地址)跟啤,同時 Lock Record 中有一個 Owner 字段存放擁有該鎖的線程的唯一標記。表示該鎖被這個線程占用唉锌。

監(jiān)視器(Monitor)
每一個對象都有一個 Monitor 對象與之關(guān)聯(lián)隅肥,當一個 Monitor 被持有后,它將處于鎖狀態(tài)袄简,synchornized 在 JVM 里面的實現(xiàn)都是基于進入和退出 Monitor 對象來實現(xiàn)方法同步和代碼塊同步腥放。通過成對的 MonitorEnter 和 MontiorExit 指令來實現(xiàn)。

MonitorEnter 指令:插入在同步代碼塊的開始位置绿语,當代碼執(zhí)行到該指令時秃症,將嘗試獲取該對象 Monitor 的所有權(quán),即嘗試獲取該對象的鎖吕粹。
MonitorExit 指令种柑,插入在方法結(jié)束和異常處,JVM 保證每個 MonitorEnter 必須有對應(yīng)的 MonitorExit

Monitor 對象存在于每個 Java 對象的對象頭 Mark Word 中匹耕,synchronidzed 鎖便是通過這種方式獲取鎖的聚请,也是為什么 Java 中任意對象都可以作為鎖的原因,同時泌神,notify良漱,notifyAll舞虱,wait 等方法會使用到 Monitor 鎖對象,所以必須在同步代碼塊中使用母市。

Monitor 是由 ObjectMonitor 實現(xiàn)的矾兜,其主要數(shù)據(jù)結(jié)構(gòu)如下:
ObjectMonitor() {
_header = NULL;
_count = 0; //記錄個數(shù)
_waiters = 0,
_recursions = 0;
_object = NULL;
_owner = NULL;
_WaitSet = NULL; //處于wait狀態(tài)的線程,會被加入到_WaitSet
_WaitSetLock = 0 ;
_Responsible = NULL ;
_succ = NULL ;
_cxq = NULL ;
FreeNext = NULL ;
_EntryList = NULL ; //處于等待鎖block狀態(tài)的線程患久,會被加入到該列表
_SpinFreq = 0 ;
_SpinClock = 0 ;
OwnerIsThread = 0 ;
}

ObjectMonitor 中有兩個隊列椅寺,_WaitSet 和 _EntryList,用來保存 ObjectWaiter 對象列表( 每個等待鎖的線程都會被封裝成 ObjectWaiter 對象)蒋失,_owner 指向持有 ObjectMonitor 對象的線程返帕,

當多個線程同時訪問一段同步代碼時,首先會進入 _EntryList 集合篙挽,當線程獲取到對象的 monitor 后進入 _Owner 區(qū)域并把 monitor 中的 owner 變量設(shè)置為當前線程荆萤,同時 monitor 中的計數(shù)器 count 加 1,

若線程調(diào)用 wait() 方法铣卡,將釋放當前持有的 monitor链韭,owner 變量恢復(fù)為 null,count 自減 1煮落,同時該線程進入 _WaitSet 集合中等待被喚醒敞峭。

若當前線程執(zhí)行完畢也將釋放 monitor(鎖) 并復(fù)位變量的值,以便其他線程進入獲取 monitor(鎖)蝉仇。

synchronized 與 Lock 鎖的比較
鎖的獲取與釋放

synchronized 是 Java 語言內(nèi)置的實現(xiàn)旋讹,使用簡單,無須關(guān)注鎖的釋放與獲取轿衔,都是 JVM 自動管理沉迹。

Lock 相關(guān)的鎖撵儿,則需要手動獲取與釋放,稍有不慎无拗,忘記釋放鎖或者程序處理異常導(dǎo)致鎖沒有釋放钥顽,則會造成死鎖。所以渺贤,通常 unLock() 操作都要在 finally 語句塊中進行釋放鎖操作。

但是,由于鎖的釋放與獲取都由程序把控摘刑,能夠?qū)崿F(xiàn)更靈活的鎖獲取與釋放。

Lock 鎖可以非阻塞刻坊、響應(yīng)中斷枷恕、響應(yīng)超時
● 使用 Lock 接口可以非阻塞地獲取鎖,具體API是tryLock()和tryLock(long time, TimeUnit unit)谭胚。
● 使用 Lock 接口可以響應(yīng)中斷和超時:synchronized一旦嘗試獲取鎖徐块,就會一直等待下去未玻,直到獲取鎖;但是Lock接口可以響應(yīng)中斷或者超時胡控。具體API是lockInterruptibly()和tryLock(long time, TimeUnit unit)扳剿。

Lock 鎖提供了更豐富的鎖
Lock 接口提供了更豐富的鎖語義:Lock接口及其實現(xiàn)類,實現(xiàn)了讀寫鎖昼激、公平鎖與非公平鎖等庇绽,讀寫鎖:ReadWriteLock;公平鎖與非公平鎖:以ReentrantLock為例橙困,只要構(gòu)造函數(shù)傳入true瞧掺,就可以使用公平鎖,默認是非公平鎖凡傅。synchronized 是支持重入的非公平鎖辟狈。

實現(xiàn)原理
Lock 接口都是基于 AQS 實現(xiàn)的,鎖的標志是 AQS 中的 state 標志位夏跷,當獲取鎖失敗后哼转,Lock 會進入自旋 +CAS 的形式實現(xiàn)的,以等待鎖的獲取拓春。

synchronized 則是通過爭搶對象關(guān)聯(lián)的 Monitor 實現(xiàn)的释簿,當線程爭搶 Monitor 失敗后,則會進入阻塞狀態(tài)硼莽;

由于 Java 的線程時映射到操作系統(tǒng)原生的線程之上的庶溶,如果阻塞或喚醒一個線程就需要操作系統(tǒng)幫忙,這時就要從用戶態(tài)轉(zhuǎn)到核心態(tài)懂鸵,因此線程狀態(tài)轉(zhuǎn)換需要花費很多的處理器時間偏螺,

對于簡單的同步塊(比如被 synchronized 修飾的 get、set 方法)匆光,往往狀態(tài)轉(zhuǎn)換消耗的時間比用戶代碼執(zhí)行的時間還要長套像,不過隨著JDK1.6后,偏向鎖终息、輕量級鎖夺巩、鎖消除、自旋鎖周崭、自適應(yīng)自旋鎖柳譬、鎖粗化的出現(xiàn),synchronized 的性能得到了很大的改善续镇。

性能比較
ReadWriteLock 接口的實現(xiàn)類美澳,可以實現(xiàn)讀鎖和寫鎖分離,極大提高了讀多寫少情況下系統(tǒng)性能。

從常規(guī)性能上來說制跟,如果資源競爭不激烈舅桩,兩者的性能是差不多的,但當競爭資源非常激烈時(即有大量線程同時競爭)雨膨,此時 Lock 的性能要遠遠優(yōu)于 synchronized擂涛。

Volatile 的實現(xiàn)原理

共享性
數(shù)據(jù)共享性是線程安全的主要原因之一。

互斥性
資源互斥是指同時只允許一個訪問者對其進行訪問哥放,具有唯一性和排它性歼指。我們通常允許多個線程同時對數(shù)據(jù)進行讀操作,但同一時間內(nèi)只允許一個線程對數(shù)據(jù)進行寫操作甥雕。
所以我們通常將鎖分為共享鎖和排它鎖踩身,也叫做讀鎖和寫鎖。

原子性
原子性就是指對數(shù)據(jù)的操作是一個獨立的社露、不可分割的整體挟阻。換句話說,就是一次操作峭弟,是一個連續(xù)不可中斷的過程附鸽,數(shù)據(jù)不會執(zhí)行的一半的時候被其他線程所修改。

可見性


333.png

每個線程都有一個自己的工作內(nèi)存(相當于CPU高級緩沖區(qū)瞒瘸,這么做的目的還是在于進一步縮小存儲系統(tǒng)與CPU之間速度的差異坷备,提高性能),對于共享變量情臭,線程每次讀取的是工作內(nèi)存中共享變量的副本省撑,寫入的時候也直接修改工作內(nèi)存中副本的值,然后在某個時間點上再將工作內(nèi)存與主內(nèi)存中的值進行同步俯在。

這樣導(dǎo)致的問題是竟秫,如果線程1對某個變量進行了修改,線程2卻有可能看不到線程1對共享變量所做的修改跷乐。

有序性
為了提高性能肥败,編譯器和處理器可能會對指令做重排序。重排序可以分為三種:
● 編譯器優(yōu)化的重排序愕提。編譯器在不改變單線程程序語義的前提下馒稍,可以重新安排語句的執(zhí)行順序。
● 指令級并行的重排序∏城龋現(xiàn)代處理器采用了指令級并行技術(shù)(Instruction-Level Parallelism筷黔, ILP)來將多條指令重疊執(zhí)行。如果不存在數(shù)據(jù)依賴性仗颈,處理器可以改變語句對應(yīng)機器指令的執(zhí)行順序。
● 內(nèi)存系統(tǒng)的重排序。由于處理器使用緩存和讀/寫緩沖區(qū)挨决,這使得加載和存儲操作看上去可能是在亂序執(zhí)行请祖。

volatile 是 輕量級的 synchronized。

volatile 的使用

  1. 防止重排序
    3 public class Singleton {
    4 public static volatile Singleton singleton;
    5
    9 private Singleton() {};
    10
    11 public static Singleton getInstance() {
    12 if (singleton == null) {
    13 synchronized (singleton) {
    14 if (singleton == null) {
    15 singleton = new Singleton();
    16 }
    17 }
    18 }
    19 return singleton;
    20 }
    21 }

實例化一個對象其實可以分為三個步驟:
 〔逼怼(1)分配內(nèi)存空間肆捕。
  (2)初始化對象盖高。
 ∩髁辍(3)將內(nèi)存空間的地址賦值給對應(yīng)的引用。

但是由于操作系統(tǒng)可以對指令進行重排序喻奥,所以上面的過程也可能會變成如下過程:
 ∠Α(1)分配內(nèi)存空間。
 ∽膊稀(2)將內(nèi)存空間的地址賦值給對應(yīng)的引用润梯。
  (3)初始化對象

這樣的話在多線程的情況下可能將一個沒實例化的對象暴露出來甥厦,造成不可預(yù)料的效果纺铭。如果加上 volatile 就可以防止這個過程重排序

  1. 保證可見性

● 修改 volatile 變量時會強制將修改后的值刷新的主內(nèi)存中。
● 修改 volatile 變量后會導(dǎo)致其他線程工作內(nèi)存中對應(yīng)的變量值失效刀疙。因此舶赔,再讀取該變量值的時候就需要重新從讀取主內(nèi)存中的值。

通過這兩個操作谦秧,就可以解決 volatile 變量的可見性問題竟纳。

  1. 保證原子性
    volatile 只能保證單次 讀/寫 的原子性

final 關(guān)鍵字
final 可以申明成員變量、方法油够、類蚁袭、本地變量。一旦將引用聲明為 final石咬,將無法再改變這個引用揩悄。final關(guān)鍵字還能保證內(nèi)存同步。

final 的方法不能被重寫鬼悠,final 的類不能被繼承删性。

final 好處
● 提高了性能,JVM在常量池中會緩存 final 變量
● final 變量在多線程中并發(fā)安全焕窝,無需額外的同步開銷
● final 方法是靜態(tài)編譯的蹬挺,提高了調(diào)用速度
● final 類創(chuàng)建的對象是只可讀的,在多線程可以安全共享

死鎖
死鎖:是指兩個或兩個以上的進程在執(zhí)行過程中它掂,因爭奪資源而造成的一種互相等待的現(xiàn)象巴帮,若無外力作用溯泣,它們都將無法推進下去。

產(chǎn)生死鎖的原因主要是:
● 因為系統(tǒng)資源不足榕茧。
● 進程運行推進的順序不合適垃沦。
● 資源分配不當?shù)取?br> 如果系統(tǒng)資源充足,進程的資源請求都能夠得到滿足用押,死鎖出現(xiàn)的可能性就很低肢簿,否則
就會因爭奪有限的資源而陷入死鎖。其次蜻拨,進程運行推進順序與速度不同池充,也可能產(chǎn)生死鎖。

產(chǎn)生死鎖的四個必要條件:

● 互斥條件:一個資源每次只能被一個進程使用缎讼。
● 占有且等待:一個進程因請求資源而阻塞時收夸,對已獲得的資源保持不放。
● 不可強行占有:進程已獲得的資源休涤,在末使用完之前咱圆,不能強行剝奪。
● 循環(huán)等待條件:若干進程之間形成一種頭尾相接的循環(huán)等待資源關(guān)系功氨。
這四個條件是死鎖的必要條件序苏,只要系統(tǒng)發(fā)生死鎖,這些條件必然成立捷凄,而只要上述條件之
一不滿足忱详,就不會發(fā)生死鎖。

處理死鎖的基本方法:

*死鎖預(yù)防:通過設(shè)置某些限制條件跺涤,去破壞死鎖的四個條件中的一個或幾個條件匈睁,來預(yù)防發(fā)生死鎖。但由于所施加的限制條件往往太嚴格桶错,因而導(dǎo)致系統(tǒng)資源利用率和系統(tǒng)吞吐量降低航唆。

*死鎖避免:允許前三個必要條件,但通過明智的選擇院刁,確保永遠不會到達死鎖點糯钙,因此死鎖避免比死鎖預(yù)防允許更多的并發(fā)。

*死鎖檢測:不須實現(xiàn)采取任何限制性措施退腥,而是允許系統(tǒng)在運行過程發(fā)生死鎖任岸,但可通過系統(tǒng)設(shè)置的檢測機構(gòu)及時檢測出死鎖的發(fā)生,并精確地確定于死鎖相關(guān)的進程和資源狡刘,然后采取適當?shù)拇胧┫砬保瑥南到y(tǒng)中將已發(fā)生的死鎖清除掉。

*死鎖解除:與死鎖檢測相配套的一種措施嗅蔬。當檢測到系統(tǒng)中已發(fā)生死鎖剑按,需將進程從死鎖狀態(tài)中解脫出來疾就。常用方法:撤銷或掛起一些進程,以便回收一些資源吕座,再將這些資源分配給已處于阻塞狀態(tài)的進程虐译。死鎖檢測盒解除有可能使系統(tǒng)獲得較好的資源利用率和吞吐量,但在實現(xiàn)上難度也最大吴趴。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市侮攀,隨后出現(xiàn)的幾起案子锣枝,更是在濱河造成了極大的恐慌,老刑警劉巖兰英,帶你破解...
    沈念sama閱讀 219,039評論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件撇叁,死亡現(xiàn)場離奇詭異,居然都是意外死亡畦贸,警方通過查閱死者的電腦和手機陨闹,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,426評論 3 395
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來薄坏,“玉大人趋厉,你說我怎么就攤上這事〗鹤梗” “怎么了君账?”我有些...
    開封第一講書人閱讀 165,417評論 0 356
  • 文/不壞的土叔 我叫張陵,是天一觀的道長沈善。 經(jīng)常有香客問我乡数,道長,這世上最難降的妖魔是什么闻牡? 我笑而不...
    開封第一講書人閱讀 58,868評論 1 295
  • 正文 為了忘掉前任净赴,我火速辦了婚禮,結(jié)果婚禮上罩润,老公的妹妹穿的比我還像新娘玖翅。我一直安慰自己,他們只是感情好哨啃,可當我...
    茶點故事閱讀 67,892評論 6 392
  • 文/花漫 我一把揭開白布烧栋。 她就那樣靜靜地躺著,像睡著了一般拳球。 火紅的嫁衣襯著肌膚如雪审姓。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,692評論 1 305
  • 那天祝峻,我揣著相機與錄音魔吐,去河邊找鬼扎筒。 笑死,一個胖子當著我的面吹牛酬姆,可吹牛的內(nèi)容都是我干的嗜桌。 我是一名探鬼主播,決...
    沈念sama閱讀 40,416評論 3 419
  • 文/蒼蘭香墨 我猛地睜開眼辞色,長吁一口氣:“原來是場噩夢啊……” “哼骨宠!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起相满,我...
    開封第一講書人閱讀 39,326評論 0 276
  • 序言:老撾萬榮一對情侶失蹤层亿,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后立美,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體匿又,經(jīng)...
    沈念sama閱讀 45,782評論 1 316
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,957評論 3 337
  • 正文 我和宋清朗相戀三年建蹄,在試婚紗的時候發(fā)現(xiàn)自己被綠了碌更。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 40,102評論 1 350
  • 序言:一個原本活蹦亂跳的男人離奇死亡洞慎,死狀恐怖痛单,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情拢蛋,我是刑警寧澤桦他,帶...
    沈念sama閱讀 35,790評論 5 346
  • 正文 年R本政府宣布,位于F島的核電站谆棱,受9級特大地震影響快压,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜垃瞧,卻給世界環(huán)境...
    茶點故事閱讀 41,442評論 3 331
  • 文/蒙蒙 一蔫劣、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧个从,春花似錦脉幢、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,996評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至奕污,卻和暖如春萎羔,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背碳默。 一陣腳步聲響...
    開封第一講書人閱讀 33,113評論 1 272
  • 我被黑心中介騙來泰國打工贾陷, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留缘眶,地道東北人。 一個月前我還...
    沈念sama閱讀 48,332評論 3 373
  • 正文 我出身青樓髓废,卻偏偏與公主長得像巷懈,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子慌洪,可洞房花燭夜當晚...
    茶點故事閱讀 45,044評論 2 355

推薦閱讀更多精彩內(nèi)容