後端---Java中的公平鎖和非公平鎖實現詳解
Java中的公平鎖和非公平鎖實現詳解
在Java中實現鎖的方式有兩種,一種是使用Java自帶的關鍵字synchronized對相應的類或者方法以及程式碼塊進行加鎖,另一種是ReentrantLock,前者只能是非公平鎖,而後者是預設非公平但可實現公平的一把鎖。
ReentrantLock
的實現是基於其內部類FairSync
(公平鎖)和NonFairSync
(非公平鎖)實現的。 其可重入性是基於Thread.currentThread()
實現的: 如果當前執行緒已經獲得了執行序列中的鎖, 那執行序列之後的所有方法都可以獲得這個鎖。
在本文將集中對ReentrantLock以及它實現公平鎖和非公平鎖的方式進行講解:
首先我們來看一段ReentrantLock的原始碼,有助於我們等會對非公平和公平鎖的理解:
public class ReentrantLockExample { int a=0; ReentrantLock lock= new ReentrantLock(); public void write() { lock.lock(); // 獲取鎖 try { a++; }finally { lock.unlock(); // 釋放鎖 } } public void reader() { lock.lock(); // 獲取鎖 try { int i=a; ... } finally { lock.unlock(); // 釋放鎖 } } }
ReentrantLock的實現依賴於Java同步器框架AbstractQueuedSynchronizer(AQS)。AQS使用一個整形的volatile變數state來維護同步狀態,這個volatile變數是實現ReentrantLock的關鍵。我再看一下下面ReentrantLock的類圖
公平鎖:
公平和非公平鎖的佇列都基於鎖內部維護的一個雙向連結串列,表結點Node的值就是每一個請求當前鎖的執行緒。公平鎖則在於每次都是依次從隊首取值。
鎖的實現方式是基於如下幾點:
表結點Node和狀態state的volatile關鍵字。
sum.misc.Unsafe.compareAndSet的原子操作。
非公平鎖:
在等待鎖的過程中, 如果有任意新的執行緒妄圖獲取鎖,都是有很大的機率直接獲取到鎖的。
ReentrantLock鎖都不會使得執行緒中斷,除非開發者自己設定了中斷位。
ReentrantLock獲取鎖裡面有看似自旋的程式碼,但是它不是自旋鎖。
ReentrantLock公平與非公平鎖都是屬於排它鎖。
ReentrantLock的可重入性分析
這裡有一篇對鎖介紹甚為詳細的文章 朱小廝的部落格-Java中的鎖.
synchronized的可重入性
參考這篇文章: Java內建鎖synchronized的可重入性。
不過考慮到基礎知識不一,如果對鎖的概念不太清晰的也可參考我這篇部落格https://blog.csdn.net/weixin_42504145/article/details/84995202 jvm---靜態方法加鎖和非靜態方法加鎖的區別
ReentrantLock的可重入性
前言裡面提到,ReentrantLock重入性是基於Thread.currentThread()實現的: 如果當前執行緒已經獲得了鎖, 那該執行緒下的所有方法都可以獲得這個鎖。ReentrantLock的鎖依賴只有 NonfairSync和FairSync兩個實現類, 他們的鎖獲取方式大同小異。
可重入性的實現基於下面程式碼片段的
else if
語句)(如果想要真正理解ReentrantLock的課重入性要仔細理解上面那片部落格,以及下面給出的else if 語句)
protected final boolean tryAcquire(int acquires) {
final Thread current = Thread.currentThread();
int c = getState(); // 獲取鎖的開始,首先讀volatile變數
if (c == 0) { // 這個if條件是沒有其他執行緒獲取該物件的鎖,我們用CAS嘗試獲取鎖
if(ifFirst(current) && compareAndSetState(0,acquires)){
setExclusiveOwnerThread(current);
return(true) // 這個方法在cas成功後,將物件的Mark Word裡的鎖標誌改成該執行緒
}
}
else if (current == getExclusiveOwnerThread()) {
// 是當前執行緒,直接獲取到鎖。實現可重入性。
int nextc = c + acquires; // acquires預設傳入1,相當於讓nextc自增
if (nextc < 0)
throw new Error("Maximum lock count exceeded");
setState(nextc); // 將標誌位改成自增後的值
return true;
}
return false;
}
此處有兩個值需要關心:
/**
* The current owner of exclusive mode synchronization.
* 持有該鎖的當前執行緒
*/
private transient Thread exclusiveOwnerThread;-----------------兩個值不在同一個類----------------
/**
* The synchronization state.
* 0: 初始狀態-無任何執行緒得到了鎖
* > 0: 被執行緒持有, 具體值表示被當前執行緒持有的執行次數
*
* 這個欄位在解鎖的時候也需要用到。
* 注意這個欄位的修飾詞: volatile
*/
private volatile int state;
公平鎖FairSync
公平鎖的實現機理在於每次有執行緒來搶佔鎖的時候,都會檢查一遍有沒有等待佇列,如果有, 當前執行緒會執行如下步驟:
if (!hasQueuedPredecessors() &&
compareAndSetState(0, acquires))
{ setExclusiveOwnerThread(current);
return true; }
其中hasQueuedPredecessors
是用於檢查是否有等待佇列的。
public final boolean hasQueuedPredecessors() {
Node t = tail; // Read fields in reverse initialization order
Node h = head;
Node s;
return h != t &&
((s = h.next) == null || s.thread != Thread.currentThread());
}
非公平鎖NonfairSync
非公平鎖在實現的時候多次強調隨機搶佔:
if (c == 0) {
if (compareAndSetState(0, acquires)) {
setExclusiveOwnerThread(current);
return true; }
}
與公平鎖的區別在於新晉獲取鎖的程序會有多次機會去搶佔鎖。如果被加入了等待佇列後則跟公平鎖沒有區別。
ReentrantLock鎖的釋放
ReentrantLock鎖的釋放是逐級釋放的,也就是說在 可重入性 場景中,必須要等到場景內所有的加鎖的方法都釋放了鎖, 當前執行緒持有的鎖才會被釋放!
釋放的方式很簡單, state欄位減一即可:
protected final boolean tryRelease(int releases) {
// releases = 1
int c = getState() - releases;
if (Thread.currentThread() != getExclusiveOwnerThread())
throw new IllegalMonitorStateException();
boolean free = false;
if (c == 0) {
free = true;
setExclusiveOwnerThread(null);
}
setState(c);
return free;
}