寫在開頭
在上幾篇博文中,我們聊到過volatile關鍵字,用它修飾變數可以保證可見性與有序性,但它並不是鎖,在使用時並不會阻塞執行緒,且不保證原子性,屬於一種輕量級、高效的同步方式,因此,如果我們的使用場景僅需要保持可見性或者有序性,那可選擇volatile,但如果必須保證原子性的話,volatile就不行了。
在Java多執行緒中,想真正的保證執行緒的安全,離不開一個東西,那就是 鎖
!我們今天就一起來學習一下Java中的鎖,以及常見鎖的分類。
認識鎖
很多面試官在問到Java鎖的時候,往往都會這樣開頭
同學,你真的瞭解Java中的鎖嗎?
在Java中鎖是一種非常重要的同步機制,經過前面的學習我們瞭解到,在併發程式設計中,經常會遇到多個執行緒訪問同一個共享資源,當多個執行緒同時對共享資源操作寫時,會導致資料不一致。透過鎖的同步機制,可以確保在某一時刻只有一個執行緒能夠訪問特定的程式碼塊或物件。
Java中實現鎖的歷程
早在jdk1.5之前就已經引入了synchronized 關鍵字,這個單詞翻譯成中文即為“同步”之意, Java 早期版本中,synchronized 屬於 重量級鎖,效率低下;不過,在 Java 6 之後, synchronized 引入了大量的最佳化如自旋鎖、適應性自旋鎖、鎖消除、鎖粗化、偏向鎖、輕量級鎖等技術來減少鎖操作的開銷,這些最佳化讓 synchronized 鎖的效率提升了很多。JDK 1.5 開始,引入了併發工具包 java.util.concurrent.locks.Lock,讓鎖的功能更加豐富。
常用的鎖
- synchronized 關鍵字鎖定程式碼庫
- 可重入鎖 java.util.concurrent.lock.ReentrantLock
- 重複讀寫鎖 java.util.concurrent.lock.ReentrantReadWriteLock
主流鎖的分類
現在鎖的分類根據不同維度大致分有7類,話不多說,先上一個思維導圖便於記憶!
悲觀鎖 / 樂觀鎖
悲觀鎖:一律會對程式碼塊進行加鎖,如 synchronized、java.util.concurrent.locks.ReentrantLock;
樂觀鎖:預設不會進行併發修改,通常採用 CAS 演算法不斷嘗試更新;
適應場景:悲觀鎖適合寫操作較多的場景,樂觀鎖適合讀操作較多的場景
自旋鎖
自旋鎖是指嘗試獲取鎖的執行緒不會立即阻塞,而是採用迴圈的方式去嘗試獲取鎖,這樣的好處是減少執行緒上下文切換的消耗,缺點是迴圈佔有、浪費 CPU 資源。
偏向鎖 / 輕量級鎖 / 重量級鎖
JDK 1.5 之後新增鎖的升級機制,提升效能。透過 synchronized 加鎖後,一段同步程式碼一直被同一個執行緒所訪問,那麼該執行緒獲取的就是偏向鎖;偏向鎖被一個其他執行緒訪問時,Java 物件的偏向鎖就會升級為輕量級鎖;再有其他執行緒會以自旋的形式嘗試獲取鎖,不會阻塞,自旋一定次數仍然未獲取到鎖,就會膨脹為重量級鎖
公平鎖 / 非公平鎖
公平鎖:多個執行緒按照申請順序來獲取鎖。如java.util.concurrent.lock.ReentrantLock.FairSync;
非公平鎖:指多個執行緒獲取鎖的順序並不是按照申請鎖的順序,有可能後申請的執行緒先獲得鎖。如 synchronized、java.util.concurrent.lock.ReentrantLock.NonfairSync。
可重入鎖
指在同一個執行緒在外層方法獲取鎖的時候,進入內層方法會自動獲取鎖。JDK 中基本都是可重入鎖,避免死鎖的發生。
獨享鎖 / 共享鎖
獨享鎖指鎖一次只能被一個執行緒所持有。synchronized、java.util.concurrent.locks.ReentrantLock 都是獨享鎖;
共享鎖:指鎖可被多個執行緒所持有。ReadWriteLock 返回的 ReadLock 就是共享鎖。
除了以上這6種鎖之外,還有的面試題中提到了粗粒度鎖與細粒度鎖
粗粒度鎖 / 細粒度鎖
粗粒度鎖:就是把執行的程式碼塊都鎖定;
細粒度鎖:就是鎖住儘可能小的程式碼塊,java.util.concurrent.ConcurrentHashMap 中的分段鎖就是一種細粒度鎖;
粗粒度鎖和細粒度鎖是相對的,沒有什麼標準。
總結
OK,今天關於鎖的介紹就寫到這裡啦,本文先做一個總體上的概括,在後續的更新中會做詳細介紹呀!😊
結尾彩蛋
如果本篇部落格對您有一定的幫助,大家記得留言+點贊+收藏呀。原創不易,轉載請聯絡Build哥!
如果您想與Build哥的關係更近一步,還可以關注“JavaBuild888”,在這裡除了看到《Java成長計劃》系列博文,還有提升工作效率的小筆記、讀書心得、大廠面經、人生感悟等等,歡迎您的加入!