多線程中的各種鎖


注意
博主是初學者,此文包含個人理解,謹慎閱讀

樂觀鎖與悲觀鎖

悲觀鎖
總是認為臨界資源會被多個線程同時爭用,於是在使用之前,先對資源加鎖,使其它線程阻塞,使用完成之后再釋放資源
樂觀鎖
認為臨界資源大多數時間不會被多個線程同時爭用,在進行修改操作時,通過某些手段,檢測有沒有其他線程使用了此共享資源,如果沒有,操作成功,如果有,拒絕訪問,並重試.

在硬件上,有專門處理器指令來處理這一過程
1.測試並設置(Test-and-Set)
2.獲取並增加(Fetch-and-Increment)
3.交換(Swap)
4.比較並交換(Compare-and-Swap) 這就是CAS
5.加載鏈接/條件儲存 (Load-Linked/Stroe-Conditional)

MySQL數據庫中,MVCC使用了基於版本號機制的樂觀鎖

公平鎖與非公平鎖

公平鎖
對於等待統一臨界資源的線程來說,資源被釋放后,先到先得
非公平鎖
無法保證下一個是誰獲取臨界資源

可重入鎖與不可重入鎖

可重入鎖
一條線程能夠反復進入被它只有鎖的同步塊.
使用一個信號值標志該臨界資源有沒有被占用,每一次進入時是信號值+1,釋放資源時-1,信號值為0則標志當前臨界資源可用

不可重入鎖
如果一個線程想要再次訪問已經被自己占用的臨界資源,可能死鎖

輕量級,重量級以及偏向鎖

重量級鎖
在java中,synchronize關鍵字表現出的語義和重量級鎖相同,到來即占用,離開即釋放

在對象頭中的Mark Word中,如果沒有上鎖,那么需要存儲對象hash碼,分代年齡等信息.
無論有沒有上鎖,Mark Word的最后兩位都是標志位,其中,01表示沒有上鎖或者是偏向鎖

如果標志位是10,即表示當前對象被重量級鎖了.
此時,對象的hash碼等信息不再存儲於Mark Word中,代表重量級鎖的ObjectMonitor類中,會保存原來Mark Word中的信息,原來的Mark Word存儲hash碼等信息的bit,用於表示指向重量級鎖的指針
注意,在這些變化中,Mark Word的最后兩個字節始終表示標志位.

重量級鎖的實現方式用到了互斥
對於synchronize關鍵字來說,JAVAC在編譯時,會產生monitorenter和monitorexit兩條字節碼指令,分別表示訪問臨界資源和釋放臨界資源.
執行monitorenter時,會使信號量(鎖計數器)加一;執行monitorexit時,會使信號量(鎖計數器)減一,當信號量為0時,就表示這臨界資源當前沒有被鎖定
由此可見,重量級鎖是可重入鎖.

在獲取鎖時,如果失敗了,那么當前線程應該被阻塞,由此可見,重量級鎖是悲觀鎖.

然而,synchronize關鍵字在JVM中被執行時,並非就一定是重量級鎖,實際上,輕量級鎖和偏向鎖是對其的一種優化

輕量級鎖
輕量級鎖的標志位是00,它的實現與重量級鎖類似,將Mark Word中的部分bit化為指針,指向Lock Record
Lock Record是輕量級鎖為Mark Word提供的一個拷貝,其中包含了原來存儲在Mark Word的相關信息.

與重量級鎖不同的是,輕量級鎖並沒有使用互斥來實現,而是使用了CAS
這就意味着,輕量級鎖實際上是一種樂觀鎖嗎?至少我現在認為,不應該說輕量級鎖是樂觀鎖.因為它也像重量級鎖一下,鎖住了臨界資源,只是,給臨界資源上鎖時,使用了樂觀鎖的方式
輕量級鎖會嘗試使用CAS把對象的Mark Word指向Lock Record,並更新標志位.
如果失敗了,當前線程會試圖使用自旋(這個稍后說)來獲取鎖,如果仍然失敗,那么此時,輕量級鎖就必須膨脹為重量級鎖,以減少自旋帶來的消耗

輕量級鎖比重量級鎖性能優化體現在,將互斥轉化為了CAS,但是如果在存在多線程爭用臨界資源的情況下,輕量級鎖的消耗要高於重量級鎖.

關於解鎖,輕量級鎖也使用了CAS

JVM在獲取輕量級鎖是,如果失敗了,會檢查對象的Mark Word是否指向了當前線程的棧幀,如果是,直接執行代碼.
由此可見,輕量級鎖也是可重入鎖

偏向鎖
偏向指的是對線程的偏向,如果一個線程獲取了鎖,那么在沒有其他線程爭用的情況下,這個鎖就一直歸這個線程所有了,不再需要解鎖等操作.

偏向鎖的標志位是01,和無鎖相同,所以需要倒數第三位表示偏向模式,倒數第三位為0表示無鎖,倒數第三位為1表示偏向鎖

偏向鎖的加鎖操作也是通過CAS來完成的,比起輕量級鎖,它避免了在單個線程中多次使用CAS來鎖住統一個對象.
但是一旦有第二個線程爭用臨界資源,偏向模式立刻結束.

值得注意的是,偏向模式直接修改了Mark Word而沒有留下備份,而在java中,JVM需要保證,沒有重寫hashcode方法的對象,其hash只能被計算一次.
所以,對於沒有重寫hashcode方法的對象,如果以及計算過hash值,那么它就無法被偏向鎖鎖定.如果處於偏向鎖狀態,計算hash值也會直接導致偏向模式的退出

這是《深入理解java虛擬機(第三版)》P482的圖
表示的是,32位Mark Word的各種狀態

JVM中的其他鎖優化

這些優化,並不一定在所有情況下都會減少鎖的消耗

自旋鎖
如果發現臨界資源已經被其他線程占用,先不進入阻塞狀態,而是在CPU上執行一個忙循環(自旋),看看能不能等到臨界資源被釋放
如果在規定時間沒有等到,那就老實去阻塞.
自適應的自旋鎖就是,根據一些變量,比如自旋成功次數等,動態決定自旋應該等待的時間

鎖消除
去掉沒有必要加的鎖

鎖粗化
增大鎖的顆粒
一般來說,鎖顆粒越小越好,有利於其他線程獲取資源,避免過多的阻塞.
但是如果在一個線程中,反復加鎖解鎖,對性能也有較大影響.

其它

不要使用擁有不可變特性的對象,作為鎖的對象,還在臨界區修改該對象


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM