鎖是java併發編程中最重要的同步機制。鎖除了讓臨界區互斥執行外,還可讓釋放鎖的線程向獲取同一個鎖的線程發送消息。鎖是解決併發衝突的重要工具。在開發中咱們會用到不少類型的鎖,每種鎖都有其自身的特色和適用範圍。須要深入理解鎖的理念和區別,才能正確、合理地使用鎖。
經常使用鎖類型
樂觀鎖與悲觀鎖
悲觀鎖對併發衝突持悲觀態度,先取鎖後訪問數據,可以較大程度確保數據安全性。
而樂觀鎖認爲數據衝突的機率比較低,能夠儘量多地訪問數據,只有在最終提交數據進行持久化時才獲取鎖。
悲觀鎖老是先獲取鎖,會增長不少額外的開銷,也增長了死鎖的概率。尤爲是對於讀操做,不會修改數據,使用悲觀鎖大大增長系統的響應時間。
樂觀鎖最後一步才提交數據,死鎖的概率比較低,可是若是有多個事務同時處理相同數據也有概率會衝突甚至致使系統異常。
傳統關係型數據庫經常使用悲觀鎖,以提升數據安全性。使用樂觀鎖的場景,一般用版本號來確保數據安全。
自旋鎖
自旋鎖會讓處於等待狀態的線程執行空循環一段時間,執行完空循環後若是可以獲取鎖就當即獲取鎖,不然才掛起線程。
使用自旋鎖,可以下降等待線程被掛起的機率。線程進入阻塞狀態再次喚醒,須要在用戶態和內核態之間進行切換,自旋鎖避免了進入內核態,所以有比較好的性能。
自旋鎖適用於競爭不激烈且線程任務執行時間短的場景。可是對於競爭激烈或者任務執行時間長的場景,不適合使用自旋鎖,不然會浪費 CPU 時間片。
重入鎖
Java 中提供的可重入鎖 ReentrantLock,是一種遞歸無阻塞的同步機制,能夠在外層方法已經加鎖的狀況下,讓內層方法再次獲取鎖。
ReentrantLock 維護了一個計數器,每加鎖一次計數器加一,解鎖一次計數器減一。Java 中的 synchronized 也是一種可重入鎖。
輪詢鎖與定時鎖
輪詢鎖是經過線程不斷嘗試獲取鎖來實現的,能夠避免發生死鎖,能夠更好地處理錯誤場景。Java 中能夠經過調用鎖的 tryLock 方法來進行輪詢。tryLock 方法還提供了一種支持定時的實現,能夠經過參數指定獲取鎖的等待時間。若是能夠當即獲取鎖那就當即返回,不然等待一段時間後返回。
讀寫鎖
讀寫鎖 ReadWriteLock 能夠優雅地實現對資源的訪問控制,具體實現爲 ReentrantReadWriteLock。讀寫鎖提供了讀鎖和寫鎖兩把鎖,在讀數據時使用讀鎖,在寫數據時使用寫鎖。
讀寫鎖容許有多個讀操做同時進行,但只容許有一個寫操做執行。若是寫鎖沒有加鎖,則讀鎖不會阻塞,不然須要等待寫入完成。
對象鎖與類鎖
能鎖對象,就不要鎖定類,儘可能控制範圍。鎖定類之後,全部的線程使用同一把鎖,同一時刻只有一個線程能夠加鎖;而鎖定對象,能夠增長鎖的數量,提升併發的效率。
注意事項
鎖的公平性
大部分鎖都支持設置公平性:公平鎖是指按照線程等待的時間來決定哪一個線程先獲取鎖,非公平鎖是指隨機選擇一個線程來獲取鎖。重入鎖和讀寫鎖默認都是非公平鎖,也能夠經過參數來設置。使用時須要根據具體場景來決定設置公平或非公平。
鎖消除
如無必要,不要使用鎖。Java 虛擬機也能夠根據逃逸分析判斷出加鎖的代碼是否線程安全,若是確認線程安全虛擬機會進行鎖消除提升效率。
鎖粗化
若是一段代碼須要使用多個鎖,建議使用一把範圍更大的鎖來提升執行效率。Java 虛擬機也會進行優化,若是發現同一個對象鎖有一系列的加鎖解鎖操做,虛擬機會進行鎖粗化來下降鎖的耗時。
以上就是一些常見的鎖型,獲取更多的知識詳解和方法,能夠私信評論我,你們一塊兒學習進步!java