溪雲閣:專注編程教學,架構,JAVA,Python,微服務,機器學習等領域,歡迎關注,一起學習。
前言 雖然很多時候用到鎖的機會不大,但是鎖的問題在面試中經常會遇到,特别是互聯網公司,面對很多高并發的時候,摳細節就成了日常,但是平時在鎖的文章上都寫得非常深奧,難以消化,筆者整理了目前JAVA裡面的鎖,用最通俗易懂的話讓大家快速記憶。
樂觀鎖
樂觀鎖是我們經常無意間用到的東西,是一種樂觀思想,這種樂觀思想就是認為:當前環境讀數據的多,寫數據的少,并發讀多,并發寫少。因此,在讀數據的時候,并不會給當前線程加鎖,在寫數據的時候,會進行判斷當前的值與期望值時候相同,如果相同則進行更新,更新期間進行加鎖,保證原子性。
這個理論應該很多人會比較熟悉,CAS理論,比較并替換,在數據庫設計中經常采用version版本号來進行樂觀鎖的實現。
悲觀鎖
相比于樂觀鎖,悲觀鎖是一種非常悲觀的思想,遇到事總是想到最壞的情況,認為寫多讀少,因此無論是讀取數據還是寫入數據,都會當作要修改其他裡面的數據,通通上鎖,指導這個線程釋放鎖後其他線程獲取。
在java裡面悲觀鎖有兩種實現:synchronized、ReentrantLock。
自旋鎖
原理:為了讓線程進行等待,讓線程不斷執行一個空操作的循環,類似你去找一個朋友,朋友在家裡幹活讓你等一下,你就在門口徘徊,不去幹别的事,徘徊了N次之後發現還沒來人,直接先去幹别的事,等他打電話叫你。
優點: 主要是為了避免線程的挂起跟喚醒的開銷,因為這部分的開銷都需要在系統的内核态中完成,然後反饋到虛拟機,這樣子的操作對虛拟機并發性能帶來了巨大的壓力。
缺點: 既然是執行空操作,必然會占用處理器的時間,當占用的時間過長的時候,處理器的資源會被白白消耗掉,而且這部分消耗是一直在做沒有任何意義的工作,性能上是非常浪費的。面對這種情況,等待的時間必須有一定的限度,如果自旋超過了限定的次數仍然沒有成功獲得鎖,就應當使用傳統的方式去挂起線程。
默認值:JVM默認值10次,配置參數為:-XX:PreBlockSpin
遞歸鎖(可重入鎖)
原理:任何線程獲取了鎖之後可以再次獲取該鎖而不會被阻塞,識别獲取鎖的線程是否為當前占據鎖的線程,如果是則再次成功獲取。獲取鎖後進行自增,
優點: 可以避免死鎖。
實現:synchronized、ReentrantLock。
讀寫鎖
讀寫鎖是通過ReentrantReadWriteLock這個類來實現,在JAVA裡面,為了提高性能而提供了這麼個東西,讀的地方用讀鎖,寫的地方用寫鎖,讀鎖并不互斥,讀寫互斥,這部分直接由JVM進行控制。
在編碼上,需要手動進行區分,下面的代碼可以看到實現方式
// 創建一個讀寫鎖 private ReentrantReadWriteLock rwLock = new ReentrantReadWriteLock(); // 獲取讀鎖 rwLock.readLock().lock(); // 釋放讀鎖 rwLock.readLock().unlock(); // 創建一個寫鎖 rwLock.writeLock().lock(); // 寫鎖 釋放 rwLock.writeLock().unlock();
公平鎖
公平鎖是一種設計思想,多線程在進行數據請求的過程中,先去隊列中申請鎖,按照FIFO先進先出的原則拿到線程,然後占有鎖。
非公平鎖
既然有公平鎖,那就有非公平鎖,也是一種設計思想。線程嘗試獲取鎖,如果獲取不到,這時候采用公平鎖的方式進行,與此同時,多個線程獲取鎖的順序有一定的随機性,并非按照先到先得的方式進行。
優點:性能上高于公平鎖
缺點:存在線程饑餓問題,存在某一個線程一直獲取不到鎖導緻一直等待,“餓死了”
在java裡面,synchronized默認就是非公平鎖,ReentrantLock可以通過構造函數來設置該鎖是公平的還是非公平的,默認是非公平的。
private final ReentrantLock.Sync sync; public ReentrantLock() { this.sync = new ReentrantLock.NonfairSync(); } public ReentrantLock(boolean fair) { this.sync = (ReentrantLock.Sync)(fair ? new ReentrantLock.FairSync() : new ReentrantLock.NonfairSync()); }
共享鎖 多個線程可以獲取讀鎖,以共享的形式持有,本質上與樂觀鎖,讀寫鎖一樣,JAVA的共享鎖也是ReentrantReadWriteLock
獨占鎖 隻有一個線程可以獲取鎖,與悲觀鎖,互斥鎖一樣,JAVA的獨占鎖有:synchronized,ReentrantLock
重量級鎖
重量級鎖其實是一種稱呼,synchronized就是一種重量級鎖,它是通過内部一個叫做監視器鎖來實現,而監視器鎖本質上是依賴于系統的Mutex Lock(互斥鎖)來實現,當加鎖的時候需要用用戶态切換為核心态,這樣子的成本非常高,因此這種依賴于操作系統Mutex Lock的鎖稱為重量級鎖。為了優化synchronized的性能,引入了輕量級鎖,偏向鎖。
輕量級鎖
在JDK1.6的時候,為了優化重量級鎖,引入了一種優化機制:輕量級鎖。由于鎖的獲取默認采用重量級,互斥的開銷很大,因此在沒有競争的時候采用CAS去操作以便消除同步使用的互斥鎖。
優點:在沒有資源競争的情況下,通過CAS操作避免了互斥鎖的開銷
缺點:如果存在競争,此時會額外增加CAS的開銷,此時導緻輕量級鎖比傳統重量級鎖更慢。
偏向鎖
除了輕量級鎖,JDK1.6還加入了另外一種鎖優化機制,偏向鎖。偏向鎖裡面最重要的一個理解就是:偏心。這個鎖會非常偏心對待第一個獲得它的線程,如果在接下來的執行過程中,該鎖一直沒有被其他的線程獲取,則持有偏向鎖的線程将永遠不需要再進行同步。
優點:針對第一個線程,連CAS都不用做了,性能上強于輕量級鎖
缺點:如果程序中的鎖總是被不同線程訪問,那這個偏向鎖就是多餘的,永遠都有第一個。
分段鎖 分段鎖算是面試中經常會被問到,希望各位記住。
在java裡面最好的實現就是ConcurrentHashMap,它裡面劃分了非常多的HashMap,默認是16個,如果需要添加一個key-value,并不是将整個HashMap鎖住,而是先進行hashcode計算從而得出這個key-value應該放在哪個HashMap裡面,然後開始對該HashMap進行加鎖,并完成put操作。在多線程中,想象一下同時進行的時候,是不是做到了真正意義上的同步進行。在這裡為了方便裡面,我用HashMap來代替Segment,其實兩者是一樣的東西,隻不過Segment是繼承了ReentrantLock來進行加鎖,非常優秀的設計。
互斥鎖 互斥鎖用最簡單的一句話來理解:某個資源隻能被一個線程訪問,讀讀,讀寫,寫讀,寫寫都是一樣的。
同步鎖 與互斥鎖一樣,在同一個時間隻允許一個線程訪問一個資源,實現用synchronized
死鎖 死鎖并不是一種思想或者技術,而是一種狀态,當線程A持有資源a,線程B持有資源b,線程A等着B釋放b,線程B等着線程A釋放a,進入了死循環,造成死鎖。
總結 JAVA裡面主要有ReentrantLock ,synchronized,Lock三種,類别也是不一樣
synchronized:屬于獨占鎖、悲觀鎖、可重入鎖、非公平鎖
ReentrantLock:繼承了Lock類,可重入鎖、悲觀鎖、獨占鎖、互斥鎖、同步鎖。
Lock:Java中的接口,可重入鎖、悲觀鎖、獨占鎖、互斥鎖、同步鎖
--END--
作者:@溪雲閣
原創作品,抄襲必究
如需要源碼,請轉發,關注後私信我
部分圖片或代碼來源網絡,如侵權請聯系删除,謝謝!
,
更多精彩资讯请关注tft每日頭條,我们将持续为您更新最新资讯!