[TOC](【後端面經-Java】公平鎖和加鎖流程) ## 1. 公平鎖和非公平鎖 ### 1.1 基本概念 - 公平鎖:線程按照到來的先後順序,排隊等待使用資源。 - 非公平鎖:線程不一定按照先後順序使用資源,而是可能出現“插隊”的情況。 拿游樂場等待娛樂項目舉例,普通游客只能按照先後順序排隊等待 ...
目錄
1. 公平鎖和非公平鎖
1.1 基本概念
- 公平鎖:線程按照到來的先後順序,排隊等待使用資源。
- 非公平鎖:線程不一定按照先後順序使用資源,而是可能出現“插隊”的情況。
拿游樂場等待娛樂項目舉例,普通游客只能按照先後順序排隊等待使用游樂設施,這就是公平鎖
,但是普通入口加上優速通,顯然VIP游客可以快人一步,這就有點非公平鎖
的意思了。
1.2 ReentrantLock 的公平鎖和非公平鎖
在《【後端面經-Java】Synchronize和ReentrantLock區別》這篇博客中,我們對比過synchronized
和ReentrantLock
的區別,其中synchronized
是一種非公平鎖
,而ReentrantLock
預設是非公平鎖
,但是也可設置為公平鎖
。
具體設置方式如下:
//生成一個公平鎖
static Lock lock = new ReentrantLock(true);
//生成一個非公平鎖
static Lock lock = new ReentrantLock(false);
static Lock lock = new ReentrantLock();//預設參數就是false,這種寫法也可
通過更改構造函數中的參數,我們可以修改ReentrantLock
的鎖類型,true
表示公平鎖,false
表示非公平鎖。構造函數具體代碼如下所示:
public ReentrantLock(boolean fair) {
sync = fair ? new FairSync() : new NonfairSync();//FairSync表示公平鎖,NonfairSync表示非公平鎖
}
2. 加鎖流程
2.1 ReentrantLock 和 AQS 的關係
在【後端面經-Java】AQS詳解這篇博客中,我們詳細講解了AQS
的原理,其中提到了
AQS定義了一套多線程訪問共用資源的同步器框架,許多同步類實現都依賴於它,如常用的ReentrantLock。
可就是說,ReentrantLock
也是通過AQS
來實現的,而自定義同步鎖需要實現AQS
框架中的tryAcquire()
和tryRelease()
方法或者tryAcquireShared()
和tryReleaseShared()
方法。
因此,ReentrantLock
的加鎖流程我們可用查看tryAcquire()
方法瞭解。
2.2 公平鎖-加鎖流程
公平鎖的tryAcquire()
方法源碼如下所示:
protected final boolean tryAcquire(int acquires) {
final Thread current = Thread.currentThread();
int c = getState();
if (c == 0) {
if (!hasQueuedPredecessors() && compareAndSetState(0,acquires)) {//這裡判斷了隊列中是不是還有其他線程在等待 && 當前資源是否可用?
//直接獲取資源
setExclusiveOwnerThread(current);
return true;
}
} else if (current == getExclusiveOwnerThread()) {//如果有其他線程在等待或者資源不可用,線程進入等待態,排隊等待
int nextc = c + acquires;
if (nextc < 0) {
throw new Error("Maximum lock count exceeded");
}
setState(nextc);
return true;
}
return false;
}
代碼流程如下所示:
- 查看是否有其他線程在等待資源。
- 如果沒有其他線程在等待,查看資源是否可用,如果資源可用,直接獲取資源。
- 如果有其他線程在等待或者資源不可用(正在被使用),線程乖乖排到隊尾,並切換為等待喚醒的休眠態。
2.3 非公平鎖-加鎖流程
非公平鎖的tryAcquire()
方法源碼如下所示:
final boolean nonfairTryAcquire(int acquires) {
final Thread current = Thread.currentThread();
int c = getState();
if (c == 0) {
if (compareAndSetState(0, acquires)) { //這裡只判斷了資源是否可用,而沒有判斷是否有其他線程在等待
setExclusiveOwnerThread(current);
return true;
}
}
else if (current == getExclusiveOwnerThread()) {
int nextc = c + acquires;
if (nextc < 0) // overflow
throw new Error("Maximum lock count exceeded");
setState(nextc);
return true;
}
return false;
}
和公平鎖
相比,非公平鎖
的加鎖流程只是少了對其他線程是否等待的判斷,因此,非公平鎖
的加鎖流程如下所示:
- 查看資源是否可用,如果資源可用,直接獲取資源。
- 如果資源不可用,不需要管是否有線程在排隊,還是排在等待隊列隊尾。
2.4 加鎖流程和性能的關係
公平鎖能保證線程獲取資源的公平性,但是性能較低;
而非公平鎖雖然無法保障公平性,但是性能更高,因此在大多數情況下,我們都會使用非公平鎖。
- 關於“公平鎖性能低,非公平鎖性能高”的解釋
理解這個結論,我們需要知道公平鎖和非公平鎖申請資源的流程。- 對於公平鎖,當一個線程創建之後,它會看是否有其他線程在等待資源,也就是看看
排隊隊伍裡面有沒有人
,如果有其他線程在等待,它就乖乖排到隊尾,並切換為等待喚醒的休眠態。而如果沒有其他線程在等待,它就直接獲取資源。 - 對於非公平鎖,當一個線程創建之後,它會直接試著去獲取資源,不管隊伍里有沒有人,如果這個時候正好資源被釋放,那麼非公平鎖因為是搶著使用資源的,提出資源申請比首個在隊列中等待的線程要早,因此資源會直接給它。如果獲取資源失敗,它才會乖乖去隊尾排隊等待。
- 對於公平鎖,當一個線程創建之後,它會看是否有其他線程在等待資源,也就是看看
對於線程狀態的切換,從休眠態到就緒態,這部分是需要時間進行上下文切換的,因此,公平鎖每次都直接進入休眠態等待被喚醒,這本身就是很耗費時間的事情,因此我們才說公平鎖性能低,非公平鎖性能高。
(非公平鎖雖然不公平,但是性能高,真的是很諷刺的一種情況吶。)
3. 面試問題模擬
Q:公平鎖是什麼?加鎖流程是什麼?
A:公平鎖是指在資源獲取過程中,線程按照到來順序排隊使用資源的一種鎖機制,而非公平鎖則可能出現不按順序的隨機獲取情況。
公平鎖的加鎖流程體現在tryAcquire()
源碼部分,當一個線程節點創建之後,它會判斷當前是否有其他線程在等待以及資源是否可用,如果兩個條件都滿足,它則獲取資源,如果不滿足,它則乖乖排到隊尾,等待被喚醒。