Lock - 對鎖的一些面試題的總結
看到一個問題:請談談你對樂觀鎖、悲觀鎖、自旋鎖、分段所、讀寫鎖、排它鎖、共享鎖等等鎖的理解,他們有什么區別?這么大致一看,發現沒什么思路,覺得對鎖的了解還是不夠透徹。這邊來總結一下,但不會很細致。
樂觀鎖 - 悲觀鎖
樂觀鎖和悲觀鎖是相對而言的,他們的區別如下表格:
鎖 | 概述 | 使用場景 | 樣例 |
---|---|---|---|
悲觀鎖 | 悲觀鎖對數據被外界修改持保守態度(悲觀),因此在整個數據處理過程中,將數據出於鎖定狀態,而別的任務出於被阻塞的狀態; | 寫多讀少,保證寫操作時的數據安全 | 1、JVM中的synchronized和Lock;2、分布式環境基於數據庫行鎖、頁鎖、表鎖、共享鎖(讀鎖)、排它鎖(寫鎖);3、基於zookeeper、Redis 的分布式鎖 |
樂觀鎖 | 樂觀鎖認為數據一般情況下不會造成沖突,所以在數據進行提交更新的時候,才會正式對數據的重提與否進行檢測,如果發現沖突了,程序自動去重試(實現通常用“版本號”) | 讀多寫少,提高系統吞吐 | 1、JDK並發包中的原子類;2、數據庫樂觀鎖、緩存樂觀鎖 |
自旋鎖
自旋鎖是互斥鎖的一種實現。在自旋鎖中,當資源被枷鎖后,其他線程想要獲取資源,此時該線程不會被阻塞睡眠而是陷入循環等待狀態(CPU不能做其它事情),循環檢查資源持有者是否已經釋放了資源(為什么叫自旋,如下圖),這樣做的好處是減少了線程從睡眠到喚醒的資源消耗,但會一直占用CPU的資源。適用於資源的鎖被持有的時間短,而又不希望在線程的喚醒上花費太多資源的情況。
分段鎖
分段鎖(SegmentLock)就是簡單的將鎖細粒度化,將一個鎖分成兩段或者多段,線程根據自己操作的段來加鎖解鎖。這樣做可以避免線程之間互相無意義的等待,減少線程的等待時間。常見的應用有ConcurrentHashMap
,它內部實現了Segment<K,V>
繼承了ReentrantLock
,分成了16段。
讀寫鎖
讀寫鎖(ReadWriteLock),顧名思義就是將讀鎖和寫鎖分離。讀寫鎖實際是一種特殊的自旋鎖,它把對共享資源的訪問者划分成讀者和寫者,讀者只對共享資源進行讀訪問,寫者則需要對共享資源進行寫操作。這種鎖相對於自旋鎖而言,能提高並發性,因為在多處理器系統中,它允許同時有多個讀者來訪問共享資源,最大可能的讀者數為實際的邏輯CPU數。寫者是排他性的,一個讀寫鎖同時只能有一個寫者或多個讀者(與CPU數相關),但不能同時既有讀者又有寫者。
在讀寫鎖保持期間也是搶占失效的。
如果讀寫鎖當前沒有讀者,也沒有寫者,那么寫者可以立刻獲得讀寫鎖,否則它必須自旋在那里,直到沒有任何寫者或讀者。如果讀寫鎖沒有寫者,那么讀者可以立即獲得該讀寫鎖,否則讀者必須自旋在那里,直到寫者釋放該讀寫鎖。
排它鎖 - 共享鎖
排它鎖又叫互斥鎖、獨占鎖、寫鎖,一個鎖在某一時刻只能被一個線程占有,其它線程必須等待鎖被釋放之后才可能獲取到鎖。如ReentrantLock
。
共享鎖又稱讀鎖,就是允許多個線程同時獲取一個鎖,一個鎖可以同時被多個線程擁有。比如說ReadWriteLock
公平鎖
公平鎖就是遵循了先到先得的原則,多個線程按照申請鎖的順序來獲取鎖。Java 中的ReentrantLock
中可以通過構造函數構建公平鎖,實現原理貌似是鏈表而不是隊列。
可重入鎖
可重入鎖的意思就是,加入方法 A 獲得鎖並加鎖之后調用了方法 B,而方法 B 也需要鎖,這樣會導致死鎖,可重入鎖則會讓調用方法 B 的時候自動獲得鎖(Java 中是通過 lockedBy
字段判斷加鎖的線程是不是同一個)