1.樂觀鎖
樂觀鎖是一種樂觀思想,即認為讀多寫少,遇到并發寫的可能性低,每次去拿資料的時候都認為別人不會修改,所以不會上鎖,但是在更新的時候會判斷一下在此期間別人有沒有去更新這個資料,采取在寫時先讀出當前版本號,然后加鎖操作(比較跟上一次的版本號,如果一樣則更新),如果失敗則要重復讀-比較-寫的操作,
java 中的樂觀鎖基本都是通過 CAS 操作實作的,CAS 是一種更新的原子操作,比較當前值跟傳入值是否一樣,一樣則更新,否則失敗,
2.悲觀鎖
悲觀鎖是就是悲觀思想,即認為寫多,遇到并發寫的可能性高,每次去拿資料的時候都認為別人會修改,所以每次在讀寫資料的時候都會上鎖,這樣別人想讀寫這個資料就會 block 直到拿到鎖,java中的悲觀鎖就是Synchronized,AQS框架下的鎖則是先嘗試cas樂觀鎖去獲取鎖,獲取不到,才會轉換為悲觀鎖,如 RetreenLock,
3.自旋鎖
自旋鎖原理非常簡單,如果持有鎖的執行緒能在很短時間內釋放鎖資源,那么那些等待競爭鎖的執行緒就不需要做內核態和用戶態之間的切換進入阻塞掛起狀態,它們只需要等一等(自旋),等持有鎖的執行緒釋放鎖后即可立即獲取鎖,這樣就避免用戶執行緒和內核的切換的消耗,
執行緒自旋是需要消耗 cpu 的,說白了就是讓 cpu 在做無用功,如果一直獲取不到鎖,那執行緒也不能一直占用 cup 自旋做無用功,所以需要設定一個自旋等待的最大時間,如果持有鎖的執行緒執行的時間超過自旋等待的最大時間扔沒有釋放鎖,就會導致其它爭用鎖的執行緒在最大等待時間內還是獲取不到鎖,這時爭用執行緒會停止自旋進入阻塞狀態,
3.1自旋鎖的優缺點
自旋鎖盡可能的減少執行緒的阻塞,這對于鎖的競爭不激烈,且占用鎖時間非常短的代碼塊來說性能能大幅度的提升,因為自旋的消耗會小于執行緒阻塞掛起再喚醒的操作的消耗,這些操作會導致執行緒發生兩次背景關系切換!
但是如果鎖的競爭激烈,或者持有鎖的執行緒需要長時間占用鎖執行同步塊,這時候就不適合使用自旋鎖了,因為自旋鎖在獲取鎖前一直都是占用 cpu 做無用功,占著 XX 不 XX,同時有大量執行緒在競爭一個鎖,會導致獲取鎖的時間很長,執行緒自旋的消耗大于執行緒阻塞掛起操作的消耗,其它需要 cup 的執行緒又不能獲取到 cpu,造成 cpu 的浪費,所以這種情況下我們要關閉自旋鎖;
4.Synchronized 同步鎖
synchronized 它可以把任意一個非 NULL 的物件當作鎖,他屬于獨占式的悲觀鎖,同時屬于可重
入鎖,
4.1 Synchronized 作用范圍
- 作用于方法時,鎖住的是物件的實體(this);
- 當作用于靜態方法時,鎖住的是Class實體,又因為Class的相關資料存盤在永久帶PermGen(jdk1.8 則是 metaspace),永久帶是全域共享的,因此靜態方法鎖相當于類的一個全域鎖,會鎖所有呼叫該方法的執行緒;
- synchronized 作用于一個物件實體時,鎖住的是所有以該物件為鎖的代碼塊,它有多個佇列,
當多個執行緒一起訪問某個物件監視器的時候,物件監視器會將這些執行緒存盤在不同的容器中,
4.2Synchronized 核心組件
- Wait Set:哪些呼叫 wait 方法被阻塞的執行緒被放置在這里;
- Contention List:競爭佇列,所有請求鎖的執行緒首先被放在這個競爭佇列中;
- Entry List:Contention List 中那些有資格成為候選資源的執行緒被移動到 Entry List 中;
- OnDeck:任意時刻,最多只有一個執行緒正在競爭鎖資源,該執行緒被成為 OnDeck;
- Owner:當前已經獲取到所資源的執行緒被稱為 Owner;
- !Owner:當前釋放鎖的執行緒,
5.ReentrantLock 可重入鎖
ReentantLock 繼承介面 Lock 并實作了介面中定義的方法,他是一種可重入鎖,除了能完成 synchronized 所能完成的所有作業外,還提供了諸如可回應中斷鎖、可輪詢鎖請求、定時鎖等避免多執行緒死鎖的方法,
5.1非公平鎖
JVM 按隨機、就近原則分配鎖的機制則稱為不公平鎖,ReentrantLock 在建構式中提供了是否公平鎖的初始化方式,默認為非公平鎖,非公平鎖實際執行的效率要遠遠超出公平鎖,除非程式有特殊需要,否則最常用非公平鎖的分配機制,
5.2公平鎖
公平鎖指的是鎖的分配機制是公平的,通常先對鎖提出獲取請求的執行緒會先被分配到鎖,ReentrantLock 在建構式中提供了是否公平鎖的初始化方式來定義公平鎖,
5.3ReentrantLock 與 synchronized
- ReentrantLock 通過方法 lock()與 unlock()來進行加鎖與解鎖操作,與 synchronized 會被 JVM 自動解鎖機制不同,ReentrantLock 加鎖后需要手動進行解鎖,為了避免程式出現例外而無法正常解鎖的情況,使用 ReentrantLock 必須在 finally 控制塊中進行解鎖操作,
- ReentrantLock 相比 synchronized 的優勢是可中斷、公平鎖、多個鎖,這種情況下需要使用 ReentrantLock,
6.ReadWriteLock 讀寫鎖
為了提高性能,Java 提供了讀寫鎖,在讀的地方使用讀鎖,在寫的地方使用寫鎖,靈活控制,如果沒有寫鎖的情況下,讀是無阻塞的,在一定程度上提高了程式的執行效率,讀寫鎖分為讀鎖和寫鎖,多個讀鎖不互斥,讀鎖與寫鎖互斥,這是由 jvm 自己控制的,你只要上好相應的鎖即可,
6.1讀鎖
如果你的代碼只讀資料,可以很多人同時讀,但不能同時寫,那就上讀鎖
6.2寫鎖
如果你的代碼修改資料,只能有一個人在寫,且不能同時讀取,那就上寫鎖,總之,讀的時候上讀鎖,寫的時候上寫鎖!java 中 讀 寫 鎖 有 個 接 口 java.util.concurrent.locks.ReadWriteLock , 也 有 具 體 的 實 現ReentrantReadWriteLock,
7.共享鎖和獨占鎖
java 并發包提供的加鎖模式分為獨占鎖和共享鎖,
7.1獨占鎖
獨占鎖模式下,每次只能有一個執行緒能持有鎖,ReentrantLock 就是以獨占方式實作的互斥鎖,
獨占鎖是一種悲觀保守的加鎖策略,它避免了讀/讀沖突,如果某個只讀執行緒獲取鎖,則其他讀執行緒都只能等待,這種情況下就限制了不必要的并發性,因為讀操作并不會影響資料的一致性,
7.2共享鎖
共享鎖則允許多個執行緒同時獲取鎖,并發訪問 共享資源,如:ReadWriteLock,共享鎖則是一種樂觀鎖,它放寬了加鎖策略,允許多個執行讀操作的執行緒同時訪問共享資源,
- AQS 的內部類 Node 定義了兩個常量 SHARED 和 EXCLUSIVE,他們分別標識 AQS 佇列中等
待執行緒的鎖獲取模式, - java 的并發包中提供了 ReadWriteLock,讀-寫鎖,它允許一個資源可以被多個讀操作訪問,或者被一個 寫操作訪問,但兩者不能同時進行,
8.重量級鎖(Mutex Lock)
Synchronized 是通過物件內部的一個叫做監視器鎖(monitor)來實作的,但是監視器鎖本質又是依賴于底層的作業系統的 Mutex Lock 來實作的,而作業系統實作執行緒之間的切換這就需要從用戶態轉換到核心態,這個成本非常高,狀態之間的轉換需要相對比較長的時間,這就是為什么
Synchronized 效率低的原因,因此,這種依賴于作業系統 Mutex Lock 所實作的鎖我們稱之為“重量級鎖”,JDK 中對 Synchronized 做的種種優化,其核心都是為了減少這種重量級鎖的使用,JDK1.6 以后,為了減少獲得鎖和釋放鎖所帶來的性能消耗,提高性能,引入了“輕量級鎖”和“偏向鎖”,
9.輕量級鎖
鎖的狀態總共有四種:無鎖狀態、偏向鎖、輕量級鎖和重量級鎖,
鎖升級
隨著鎖的競爭,鎖可以從偏向鎖升級到輕量級鎖,再升級的重量級鎖(但是鎖的升級是單向的,也就是說只能從低到高升級,不會出現鎖的降級),“輕量級”是相對于使用作業系統互斥量來實作的傳統鎖而言的,但是,首先需要強調一點的是,輕量級鎖并不是用來代替重量級鎖的,它的本意是在沒有多執行緒競爭的前提下,減少傳統的重量級鎖使用產生的性能消耗,在解釋輕量級鎖的執行程序之前,先明白一點,輕量級鎖所適應的場景是執行緒交替執行同步塊的情況,如果存在同一時間訪問同一鎖的情況,就會導致輕量級鎖膨脹為重量級鎖,
10.偏向鎖
Hotspot 的作者經過以往的研究發現大多數情況下鎖不僅不存在多執行緒競爭,而且總是由同一執行緒多次獲得,偏向鎖的目的是在某個執行緒獲得鎖之后,消除這個執行緒鎖重入(CAS)的開銷,看起來讓這個執行緒得到了偏護,引入偏向鎖是為了在無多執行緒競爭的情況下盡量減少不必要的輕量級鎖執行路徑,因為輕量級鎖的獲取及釋放依賴多次 CAS 原子指令,而偏向鎖只需要在置換ThreadID 的時候依賴一次 CAS 原子指令(由于一旦出現多執行緒競爭的情況就必須撤銷偏向鎖,所以偏向鎖的撤銷操作的性能損耗必須小于節省下來的 CAS 原子指令的性能消耗),上面說過,輕量級鎖是為了在執行緒交替執行同步塊時提高性能,而偏向鎖則是在只有一個執行緒執行同步塊時進一步提高性能,
11.分段鎖
分段鎖也并非一種實際的鎖,而是一種思想 ConcurrentHashMap 是學習分段鎖的最好實踐
轉載請註明出處,本文鏈接:https://www.uj5u.com/houduan/290244.html
標籤:java
