Java公平鎖與非公平鎖的核心原理講解
Lock鎖接口方法
前面了解到了synchronized鎖,也知道了synchronized鎖是一種JVM提供內(nèi)置鎖,但synchronized有一些缺點:比如不支持響應中斷,不支持超時,不支持以非阻塞的方式獲取鎖等。而今天的主角Lock鎖,需要我們手動獲取鎖和釋放鎖,里面有很多方式來獲取鎖,比如以阻塞方式獲取鎖,在指定時間內(nèi)獲取鎖,非阻塞模式下?lián)屨兼i等,其方法源碼如下(位于package java.util.concurrent.locks包下):
//Lock接口下的方法
public interface Lock {
//阻塞式搶占鎖,如果搶到鎖,則向下執(zhí)行程序;搶占失敗線程阻塞,直到釋放鎖才會進行搶占鎖
void lock();
//可中斷模式搶占鎖,線程調(diào)用此方法能夠中斷線程
void lockInterruptibly() throws InterruptedException;
//非阻塞式嘗試獲取鎖,調(diào)用此方法線程不會阻塞,搶到鎖返回true,失敗返回false
boolean tryLock();
//在指定時間內(nèi)嘗試獲取鎖,在指定時間內(nèi)搶到鎖成功返回true,失敗返回false
boolean tryLock(long time, TimeUnit unit) throws InterruptedException;
//釋放鎖,線程執(zhí)行完程序后,調(diào)用此方法來釋放鎖資源
void unlock();
//條件隊列
Condition newCondition();
}公平鎖簡介
公平鎖,顧名思義,所有線程獲取鎖都是公平的。在多線程并發(fā)情況下,線程爭搶鎖時,首先會檢查等待隊列中是否有其他線程在等待。如果等待隊列為空,沒有線程在等待,那么當前線程會拿到鎖資源;如果等待隊列中有其他線程在等待,那么當前線程會排到等待隊列的尾部,好比我們排隊買東西一樣。
ReentranLock的公平鎖
ReentranLock類實現(xiàn)了Lock接口,重寫了里面的方法,對于ReentranLock類中的公平鎖,我們可以看到如下源碼:
public ReentrantLock(boolean fair) {
sync = fair ? new FairSync() : new NonfairSync();
}
在上述構造方法中,新建鎖對象是否為公平鎖,在于傳入的參數(shù)是true還是false,在三目運算符中,如果傳入?yún)?shù)為true,則會創(chuàng)建一個FairSync()對象賦值給sync,線程獲取的鎖是公平鎖;如果為false則創(chuàng)建一個NonfairSync()對象,線程獲取的鎖是非公平鎖。點入FairSync()方法,得到如下源碼:
static final class FairSync extends Sync {
private static final long serialVersionUID = -3000897897090466540L;
final void lock() {
acquire(1);
}
/*這里將acquire方法放于此
public final void acquire(int arg) {
if (!tryAcquire(arg) &&
acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
selfInterrupt();
}
*/
protected final boolean tryAcquire(int acquires) {
final Thread current = Thread.currentThread();
//拿到當前鎖對象的狀態(tài)
int c = getState();
//如果沒有線程獲取到鎖
if (c == 0) {
//首先會判斷是否有前驅節(jié)點,如果沒有就會調(diào)用CAS機制更新鎖對象狀態(tài)
if (!hasQueuedPredecessors() &&
compareAndSetState(0, acquires)) {
//設置當前線程擁有鎖資源
setExclusiveOwnerThread(current);
//如果獲取鎖資源成功則返回true
return true;
}
}
//或者如果拿到鎖的就是當前線程
else if (current == getExclusiveOwnerThread()) {
//將鎖的狀態(tài)+1,考慮到鎖重入
int nextc = c + acquires;
if (nextc < 0)
//超過了最大鎖的數(shù)量
throw new Error("Maximum lock count exceeded");
//如果鎖數(shù)量沒有溢出,設置當前鎖狀態(tài)
setState(nextc);
//如果成功獲取鎖資源則返回true
return true;
}
//如果前兩條都不符合,則返回false
return false;
}
}
上述代碼涉及到了hasQueuedPredecessors()方法,返回true則代表有有前驅節(jié)點,點入查看得到以下源碼:
public final boolean hasQueuedPredecessors() {
Node t = tail; // Read fields in reverse initialization order
Node h = head;
Node s;
//h!=t,首節(jié)點不等于尾節(jié)點表示隊列中有節(jié)點
return h != t &&
(
//s為頭結點的下一個節(jié)點,返回false表示隊列中還有第二個節(jié)點,或運算符后面表示,第二個線程不是當前線程
(s = h.next) == null || s.thread != Thread.currentThread()
);
}
因此做出總結,使用ReentranLock的公平鎖,當線程調(diào)用ReentranLock類中的lock()方法時,會首先調(diào)用FairSync類中的lock()方法;然后FairSync類中的lock()方法會調(diào)用AQS類(AbstractQueuedSynchronizer)中的acquire(1)方法獲取資源,前面的文章里也提到過,acquire()方法會調(diào)用tryAcquire()方法嘗試獲取鎖,tryAcquire()方法里面沒有具體的實現(xiàn),tryAcquire()方法具體邏輯是由其子類實現(xiàn)的,因此調(diào)用的還是FairSync類中的方法。在AQS中的acquire()方法中如果嘗試獲取資源失敗,會調(diào)用addWaiter()方法將當前線程封裝為Node節(jié)點放到等待隊列的尾部,并且調(diào)用AQS中的acquireQueued方法使線程在等待隊列中排隊。
ReentranLock的非公平鎖
非公平鎖就是所有搶占鎖的線程都是不公平的,在我們?nèi)粘I钪芯拖喈斢谑遣尻牞F(xiàn)象,不過也與插隊稍微不同。在多線程并發(fā)時,每個線程在搶占鎖的過程中,都會先嘗試獲取鎖,如果獲取成功,則直接指向具體的業(yè)務邏輯;如果獲取鎖失敗,則會像公平鎖一樣在等待隊列隊尾等待。
在非公平鎖模式下,由于剛來的線程可以在隊首位置進行一次插隊,所以當插隊成功時,后面的線程可能會出現(xiàn)長時間等待,無法獲取鎖資源產(chǎn)生饑餓現(xiàn)象。但是非公平鎖性能比公平鎖性能更好。
對于ReentranLock類中的非公平鎖,實現(xiàn)方式有兩種,一種是默認的構造方式,另一種是和上面的一樣,源碼如下:
//第一種方式,默認實現(xiàn)
public ReentrantLock() {
sync = new NonfairSync();
}
//第二種方式,傳入false來創(chuàng)建非公平鎖對象
public ReentrantLock(boolean fair) {
sync = fair ? new FairSync() : new NonfairSync();
}
對于創(chuàng)建的NonfairSync()類對象,其源碼如下:
static final class NonfairSync extends Sync {
private static final long serialVersionUID = 7316153563782823691L;
/**
* Performs lock. Try immediate barge, backing up to normal
* acquire on failure.
*/
final void lock() {
if (compareAndSetState(0, 1))
setExclusiveOwnerThread(Thread.currentThread());
else
acquire(1);
}
protected final boolean tryAcquire(int acquires) {
return nonfairTryAcquire(acquires);
}
}

在上述代碼中,當線程獲取鎖時,并沒有直接將當前線程放入等待隊列中,而是先嘗試獲取鎖資源,如果獲取鎖成功,設置state標志位1成功,則直接將當前線程拿到鎖,執(zhí)行線程業(yè)務;如果獲取鎖資源失敗,則調(diào)用AQS中的acquire方法獲取資源,而acquire()方法會回調(diào)上面NonfairSyn類中的tryAcquire()方法,然后又會回調(diào)Sync類中的nonfairTryAcquire()方法(NonfairSync類繼承了Sync類) ,點擊nonfairTryAcquire(acquires)查看源碼:
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;
}
由上訴代碼,沒有將線程放入到等待隊列中,只是對鎖的狀態(tài)進行了判斷,若標識為0,代表沒有線程拿到鎖,當前線程會使用CAS機制改變鎖狀態(tài),并調(diào)用setExclusiveOwnerThread(current)方法讓當前線程拿到鎖。
因此綜上所述,在使用ReentranLock中的非公平鎖時,首先會調(diào)用lock()方法,,而ReentranLock類中l(wèi)ock()方法會調(diào)用NonfairSync類中的lock()方法,接著NonfairSync類中的lock()方法會調(diào)用AQS中的acquire()方法來獲取鎖資源,AQS中的acquire()方法又會回調(diào)NonfairSync類中tryAcquire()方法嘗試獲取資源,NonfairSync類中tryAcquire()方法會調(diào)用Sync類中的nonfairTryAcquire方法嘗試非公平鎖獲取資源;獲取失敗的話,AQS中的acquire()方法會調(diào)用addWaiter()方法將當前線程封裝成Node節(jié)點放入到等待隊列的隊尾,而后AQS中的acquire()方法會調(diào)用AQS中的acquireQueued()方法讓線程在等待隊列中排隊。這就是非公平鎖的整個流程。
到此這篇關于Java公平鎖與非公平鎖的核心原理講解的文章就介紹到這了,更多相關Java公平鎖 非公平鎖內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關文章希望大家以后多多支持腳本之家!
相關文章
KotlinScript構建SpringBootStarter保姆級教程
這篇文章主要為大家介紹了KotlinScript構建SpringBootStarter的保姆級教程,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步,早日升職加薪2022-09-09
SpringBoot+Mybatis項目使用Redis做Mybatis的二級緩存的方法
本篇文章主要介紹了SpringBoot+Mybatis項目使用Redis做Mybatis的二級緩存的方法,具有一定的參考價值,感興趣的小伙伴們可以參考一下2017-12-12
Mybatis-Plus之ID自動增長的設置實現(xiàn)
本文主要介紹了Mybatis-Plus之ID自動增長的設置實現(xiàn),文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友們下面隨著小編來一起學習學習吧2022-07-07
java發(fā)起http請求調(diào)用post與get接口的方法實例
在實際開發(fā)過程中,我們經(jīng)常需要調(diào)用對方提供的接口或測試自己寫的接口是否合適,下面這篇文章主要給大家介紹了關于java發(fā)起http請求調(diào)用post與get接口的相關資料,需要的朋友可以參考下2022-08-08

