花了幾天時間熬夜整理出來的并發鎖知識點,我說是全網最詳細就是全網最詳細,不允許反駁~

話不多說直接開始,開始之前,為方便大家記憶,記得點贊收藏加關注哦 ,需要下載PDF版本的朋友可以點一點下方鏈接找我免費領取
鏈接:1103806531暗號:CSDN
1. 并發鎖簡介
確保執行緒安全最常見的做法是利用鎖機制(Lock、sychronized)來對共享資料做互斥同步,這樣在同一個時刻,只有一個執行緒可以執行某個方法或者某個代碼塊,那么操作必然是原子性的,執行緒安全的,
在作業、面試中,經常會聽到各種五花八門的鎖,聽的人云里霧里,鎖的概念術語很多,它們是針對不同的問題所提出的,通過簡單的梳理,也不難理解,
1.1. 可重入鎖
可重入鎖,顧名思義,指的是執行緒可以重復獲取同一把鎖,即同一個執行緒在外層方法獲取了鎖,在進入內層方法會自動獲取鎖,
可重入鎖可以在一定程度上避免死鎖,
- ReentrantLock 、ReentrantReadWriteLock 是可重入鎖,這點,從其命名也不難看出,
- synchronized 也是一個可重入鎖,
【示例】synchronized 的可重入示例
synchronized void setA() throws Exception{
Thread.sleep(1000);
setB();
}
synchronized void setB() throws Exception{
Thread.sleep(1000);
}
上面的代碼就是一個典型場景:如果使用的鎖不是可重入鎖的話,setB 可能不會被當前執行緒執行,從而造成死鎖,
【示例】ReentrantLock 的可重入示例
class Task {
private int value;
private final Lock lock = new ReentrantLock();
public Task() {
this.value = 0;
}
public int get() {
// 獲取鎖
lock.lock();
try {
return value;
} finally {
// 保證鎖能釋放
lock.unlock();
}
}
public void addOne() {
// 獲取鎖
lock.lock();
try {
// 注意:此處已經成功獲取鎖,進入 get 方法后,又嘗試獲取鎖,
// 如果鎖不是可重入的,會導致死鎖
value = 1 + get();
} finally {
// 保證鎖能釋放
lock.unlock();
}
}
}
1.2. 公平鎖與非公平鎖
- 公平鎖 - 公平鎖是指 多執行緒按照申請鎖的順序來獲取鎖,
- 非公平鎖 - 非公平鎖是指 多執行緒不按照申請鎖的順序來獲取鎖 ,這就可能會出現優先級反轉(后來者居上)或者饑餓現象(某執行緒總是搶不過別的執行緒,導致始終無法執行),
公平鎖為了保證執行緒申請順序,勢必要付出一定的性能代價,因此其吞吐量一般低于非公平鎖,
公平鎖與非公平鎖 在 Java 中的典型實作:
- synchronized 只支持非公平鎖,
- ReentrantLock 、ReentrantReadWriteLock,默認是非公平鎖,但支持公平鎖,
需要更多資料的朋友可以點一點下方鏈接找我免費領取~快來找我玩
鏈接:1103806531暗號:CSDN

1.3. 獨享鎖與共享鎖
獨享鎖與共享鎖是一種廣義上的說法,從實際用途上來看,也常被稱為互斥鎖與讀寫鎖,
- 獨享鎖 - 獨享鎖是指 鎖一次只能被一個執行緒所持有,
- 共享鎖 - 共享鎖是指 鎖可被多個執行緒所持有,
獨享鎖與共享鎖在 Java 中的典型實作:
- synchronized 、ReentrantLock 只支持獨享鎖,
- ReentrantReadWriteLock 其寫鎖是獨享鎖,其讀鎖是共享鎖,讀鎖是共享鎖使得并發讀是非常高效的,讀寫,寫讀 ,寫寫的程序是互斥的,
1.4. 悲觀鎖與樂觀鎖
樂觀鎖與悲觀鎖不是指具體的什么型別的鎖,而是處理并發同步的策略,
- 悲觀鎖 - 悲觀鎖對于并發采取悲觀的態度,認為:不加鎖的并發操作一定會出問題,悲觀鎖適合寫操作頻繁的場景,
- 樂觀鎖 - 樂觀鎖對于并發采取樂觀的態度,認為:不加鎖的并發操作也沒什么問題,對于同一個資料的并發操作,是不會發生修改的,在更新資料的時候,會采用不斷嘗試更新的方式更新資料,樂觀鎖適合讀多寫少的場景,
悲觀鎖與樂觀鎖在 Java 中的典型實作:
- 悲觀鎖在 Java 中的應用就是通過使用 synchronized 和 Lock 顯示加鎖來進行互斥同步,這是一種阻塞同步,
- 樂觀鎖在 Java 中的應用就是采用 CAS 機制(CAS 操作通過 Unsafe 類提供,但這個類不直接暴露為 API,所以都是間接使用,如各種原子類),
1.5. 偏向鎖、輕量級鎖、重量級鎖
所謂輕量級鎖與重量級鎖,指的是鎖控制粒度的粗細,顯然,控制粒度越細,阻塞開銷越小,并發性也就越高,
Java 1.6 以前,重量級鎖一般指的是 synchronized ,而輕量級鎖指的是 volatile,
Java 1.6 以后,針對 synchronized 做了大量優化,引入 4 種鎖狀態: 無鎖狀態、偏向鎖、輕量級鎖和重量級鎖,鎖可以單向的從偏向鎖升級到輕量級鎖,再從輕量級鎖升級到重量級鎖 ,
- 偏向鎖 - 偏向鎖是指一段同步代碼一直被一個執行緒所訪問,那么該執行緒會自動獲取鎖,降低獲取鎖的代價,
- 輕量級鎖 - 是指當鎖是偏向鎖的時候,被另一個執行緒所訪問,偏向鎖就會升級為輕量級鎖,其他執行緒會通過自旋的形式嘗試獲取鎖,不會阻塞,提高性能,
- 重量級鎖 - 是指當鎖為輕量級鎖的時候,另一個執行緒雖然是自旋,但自旋不會一直持續下去,當自旋一定次數的時候,還沒有獲取到鎖,就會進入阻塞,該鎖膨脹為重量級鎖,重量級鎖會讓其他申請的執行緒進入阻塞,性能降低,
1.6. 分段鎖
分段鎖其實是一種鎖的設計,并不是具體的一種鎖,所謂分段鎖,就是把鎖的物件分成多段,每段獨立控制,使得鎖粒度更細,減少阻塞開銷,從而提高并發性,這其實很好理解,就像高速公路上的收費站,如果只有一個收費口,那所有的車只能排成一條隊繳費;如果有多個收費口,就可以分流了,
Hashtable 使用 synchronized 修飾方法來保證執行緒安全性,那么面對執行緒的訪問,Hashtable 就會鎖住整個物件,所有的其它執行緒只能等待,這種阻塞方式的吞吐量顯然很低,
Java 1.7 以前的 ConcurrentHashMap 就是分段鎖的典型案例,ConcurrentHashMap 維護了一個 Segment 陣列,一般稱為分段桶,
final Segment<K,V>[] segments;
當有執行緒訪問 ConcurrentHashMap 的資料時,ConcurrentHashMap 會先根據 hashCode 計算出資料在哪個桶(即哪個 Segment),然后鎖住這個 Segment,
1.7. 顯示鎖和內置鎖
Java 1.5 之前,協調對共享物件的訪問時可以使用的機制只有 synchronized 和 volatile,這兩個都屬于內置鎖,即鎖的申請和釋放都是由 JVM 所控制,
Java 1.5 之后,增加了新的機制:ReentrantLock、ReentrantReadWriteLock ,這類鎖的申請和釋放都可以由程式所控制,所以常被稱為顯示鎖,
注意:如果不需要 ReentrantLock、ReentrantReadWriteLock 所提供的高級同步特性,應該優先考慮使用 synchronized ,理由如下:
- Java 1.6 以后,synchronized 做了大量的優化,其性能已經與 ReentrantLock、ReentrantReadWriteLock 基本上持平,
- 從趨勢來看,Java 未來更可能會優化 synchronized ,而不是 ReentrantLock、ReentrantReadWriteLock ,因為 synchronized 是 JVM內置屬性,它能執行一些優化,
- ReentrantLock、ReentrantReadWriteLock 申請和釋放鎖都是由程式控制,如果使用不當,可能造成死鎖,這是很危險的,
以下對比一下顯示鎖和內置鎖的差異:
主動獲取鎖和釋放鎖
- synchronized 不能主動獲取鎖和釋放鎖,獲取鎖和釋放鎖都是 JVM 控制的,
- ReentrantLock 可以主動獲取鎖和釋放鎖,(如果忘記釋放鎖,就可能產生死鎖),
回應中斷
- synchronized 不能回應中斷,
- ReentrantLock 可以回應中斷,
超時機制
- synchronized 沒有超時機制,
- ReentrantLock 有超時機制,ReentrantLock 可以設定超時時間,超時后自動釋放鎖,避免一直等待,
支持公平鎖
- synchronized 只支持非公平鎖,
- ReentrantLock 支持非公平鎖和公平鎖,
是否支持共享
- 被 synchronized 修飾的方法或代碼塊,只能被一個執行緒訪問(獨享),如果這個執行緒被阻塞,其他執行緒也只能等待
- ReentrantLock 可以基于 Condition 靈活的控制同步條件,
是否支持讀寫分離
- synchronized 不支持讀寫鎖分離;
- ReentrantReadWriteLock 支持讀寫鎖,從而使阻塞讀寫的操作分開,有效提高并發性,
2. Lock 和 Condition
2.1. 為何引入 Lock 和 Condition
并發編程領域,有兩大核心問題:一個是互斥,即同一時刻只允許一個執行緒訪問共享資源;另一個是同步,即執行緒之間如何通信、協作,這兩大問題,管程都是能夠解決的,Java SDK 并發包通過 Lock 和 Condition 兩個介面來實作管程,其中 Lock 用于解決互斥問題,Condition 用于解決同步問題,
synchronized 是管程的一種實作,既然如此,何必再提供 Lock 和 Condition,
JDK 1.6 以前,synchronized 還沒有做優化,性能遠低于 Lock,但是,性能不是引入 Lock 的最重要因素,真正關鍵在于:synchronized 使用不當,可能會出現死鎖,
synchronized 無法通過破壞不可搶占條件來避免死鎖,原因是 synchronized 申請資源的時候,如果申請不到,執行緒直接進入阻塞狀態了,而執行緒進入阻塞狀態,啥都干不了,也釋放不了執行緒已經占有的資源,
與內置鎖 synchronized 不同的是,Lock 提供了一組無條件的、可輪詢的、定時的以及可中斷的鎖操作,所有獲取鎖、釋放鎖的操作都是顯式的操作,
-
能夠回應中斷,synchronized 的問題是,持有鎖 A 后,如果嘗試獲取鎖 B 失敗,那么執行緒就進入阻塞狀態,一旦發生死鎖,就沒有任何機會來喚醒阻塞的執行緒,但如果阻塞狀態的執行緒能夠回應中斷信號,也就是說當我們給阻塞的執行緒發送中斷信號的時候,能夠喚醒它,那它就有機會釋放曾經持有的鎖A,這樣就破壞了不可搶占條件了,
-
支持超時,如果執行緒在一段時間之內沒有獲取到鎖,不是進入阻塞狀態,而是回傳一個錯誤,那這個執行緒也有機會釋放曾經持有的鎖,這樣也能破壞不可搶占條件,
-
非阻塞地獲取鎖,如果嘗試獲取鎖失敗,并不進入阻塞狀態,而是直接回傳,那這個執行緒也有機會釋放曾經持有的鎖,這樣也能破壞不可搶占條件,
2.2. Lock 介面
Lock 的介面定義如下:
public interface Lock {
void lock();
void lockInterruptibly() throws InterruptedException;
boolean tryLock();
boolean tryLock(long time, TimeUnit unit) throws InterruptedException;
void unlock();
Condition newCondition();
}
- lock() - 獲取鎖,
- unlock() - 釋放鎖,
- tryLock() - 嘗試獲取鎖,僅在呼叫時鎖未被另一個執行緒持有的情況下,才獲取該鎖,
- tryLock(long time, TimeUnit unit) - 和 tryLock() 類似,區別僅在于限定時間,如果限定時間內未獲取到鎖,視為失敗,
- lockInterruptibly() - 鎖未被另一個執行緒持有,且執行緒沒有被中斷的情況下,才能獲取鎖,
- newCondition() - 回傳一個系結到 Lock 物件上的 Condition 實體,
2.3. Condition
Condition 實作了管程模型里面的條件變數,
前文中提過 Lock 介面中 有一個 newCondition() 方法用于回傳一個系結到 Lock 物件上的 Condition 實體,Condition 是什么?有什么作用?本節將一一講解,
在單執行緒中,一段代碼的執行可能依賴于某個狀態,如果不滿足狀態條件,代碼就不會被執行(典型的場景,如:if … else …),在并發環境中,當一個執行緒判斷某個狀態條件時,其狀態可能是由于其他執行緒的操作而改變,這時就需要有一定的協調機制來確保在同一時刻,資料只能被一個執行緒鎖修改,且修改的資料狀態被所有執行緒所感知,
Java 1.5 之前,主要是利用 Object 類中的 wait、notify、notifyAll 配合 synchronized 來進行執行緒間通信 ,
wait、notify、notifyAll 需要配合 synchronized 使用,不適用于 Lock,而使用 Lock 的執行緒,彼此間通信應該使用 Condition ,這可以理解為,什么樣的鎖配什么樣的鑰匙,內置鎖(synchronized)配合內置條件佇列(wait、notify、notifyAll ),顯式鎖(Lock)配合顯式條件佇列(Condition ),
Condition 的特性
Condition 介面定義如下:
public interface Condition {
void await() throws InterruptedException;
void awaitUninterruptibly();
long awaitNanos(long nanosTimeout) throws InterruptedException;
boolean await(long time, TimeUnit unit) throws InterruptedException;
boolean awaitUntil(Date deadline) throws InterruptedException;
void signal();
void signalAll();
}
其中,await、signal、signalAll 與 wait、notify、notifyAll 相對應,功能也相似,除此以外,Condition 相比內置條件佇列( wait、notify、notifyAll ),提供了更為豐富的功能:
- 每個鎖(Lock)上可以存在多個 Condition,這意味著鎖的狀態條件可以有多個,
- 支持公平的或非公平的佇列操作,
- 支持可中斷的條件等待,相關方法:awaitUninterruptibly() ,
- 支持可定時的等待,相關方法:awaitNanos(long) 、await(long, TimeUnit)、awaitUntil(Date),
最后
由于精力有限,暫時只寫這一部分,其余部分之后再找時間補上,現在需要完整版PDF的朋友可以點一點下方鏈接免費領取
鏈接:1103806531暗號:CSDN
在這里也為大家整理了各個知識點模塊整理檔案(微服務、資料庫、mysql、jvm、Redis等都有)和更多大廠面試真題,有需要的朋友可以點一點上方鏈接免費領取


轉載請註明出處,本文鏈接:https://www.uj5u.com/yidong/204627.html
標籤:其他
