Java公平鎖與非公平鎖的核心原理講解
Lock鎖接口方法
前面了解到了synchronized鎖,也知道了synchronized鎖是一種JVM提供內置鎖,但synchronized有一些缺點:比如不支持響應中斷,不支持超時,不支持以非阻塞的方式獲取鎖等。而今天的主角Lock鎖,需要我們手動獲取鎖和釋放鎖,里面有很多方式來獲取鎖,比如以阻塞方式獲取鎖,在指定時間內獲取鎖,非阻塞模式下?lián)屨兼i等,其方法源碼如下(位于package java.util.concurrent.locks包下):
//Lock接口下的方法 public interface Lock { //阻塞式搶占鎖,如果搶到鎖,則向下執(zhí)行程序;搶占失敗線程阻塞,直到釋放鎖才會進行搶占鎖 void lock(); //可中斷模式搶占鎖,線程調用此方法能夠中斷線程 void lockInterruptibly() throws InterruptedException; //非阻塞式嘗試獲取鎖,調用此方法線程不會阻塞,搶到鎖返回true,失敗返回false boolean tryLock(); //在指定時間內嘗試獲取鎖,在指定時間內搶到鎖成功返回true,失敗返回false boolean tryLock(long time, TimeUnit unit) throws InterruptedException; //釋放鎖,線程執(zhí)行完程序后,調用此方法來釋放鎖資源 void unlock(); //條件隊列 Condition newCondition(); }
公平鎖簡介
公平鎖,顧名思義,所有線程獲取鎖都是公平的。在多線程并發(fā)情況下,線程爭搶鎖時,首先會檢查等待隊列中是否有其他線程在等待。如果等待隊列為空,沒有線程在等待,那么當前線程會拿到鎖資源;如果等待隊列中有其他線程在等待,那么當前線程會排到等待隊列的尾部,好比我們排隊買東西一樣。
ReentranLock的公平鎖
ReentranLock類實現(xiàn)了Lock接口,重寫了里面的方法,對于ReentranLock類中的公平鎖,我們可以看到如下源碼:
public ReentrantLock(boolean fair) { sync = fair ? new FairSync() : new NonfairSync(); }
在上述構造方法中,新建鎖對象是否為公平鎖,在于傳入的參數(shù)是true還是false,在三目運算符中,如果傳入參數(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é)點,如果沒有就會調用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的公平鎖,當線程調用ReentranLock類中的lock()方法時,會首先調用FairSync類中的lock()方法;然后FairSync類中的lock()方法會調用AQS類(AbstractQueuedSynchronizer)中的acquire(1)方法獲取資源,前面的文章里也提到過,acquire()方法會調用tryAcquire()方法嘗試獲取鎖,tryAcquire()方法里面沒有具體的實現(xiàn),tryAcquire()方法具體邏輯是由其子類實現(xiàn)的,因此調用的還是FairSync類中的方法。在AQS中的acquire()方法中如果嘗試獲取資源失敗,會調用addWaiter()方法將當前線程封裝為Node節(jié)點放到等待隊列的尾部,并且調用AQS中的acquireQueued方法使線程在等待隊列中排隊。
ReentranLock的非公平鎖
非公平鎖就是所有搶占鎖的線程都是不公平的,在我們日常生活中就相當于是插隊現(xiàn)象,不過也與插隊稍微不同。在多線程并發(fā)時,每個線程在搶占鎖的過程中,都會先嘗試獲取鎖,如果獲取成功,則直接指向具體的業(yè)務邏輯;如果獲取鎖失敗,則會像公平鎖一樣在等待隊列隊尾等待。
在非公平鎖模式下,由于剛來的線程可以在隊首位置進行一次插隊,所以當插隊成功時,后面的線程可能會出現(xià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è)務;如果獲取鎖資源失敗,則調用AQS中的acquire方法獲取資源,而acquire()方法會回調上面NonfairSyn類中的tryAcquire()方法,然后又會回調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),并調用setExclusiveOwnerThread(current)方法讓當前線程拿到鎖。
因此綜上所述,在使用ReentranLock中的非公平鎖時,首先會調用lock()方法,,而ReentranLock類中l(wèi)ock()方法會調用NonfairSync類中的lock()方法,接著NonfairSync類中的lock()方法會調用AQS中的acquire()方法來獲取鎖資源,AQS中的acquire()方法又會回調NonfairSync類中tryAcquire()方法嘗試獲取資源,NonfairSync類中tryAcquire()方法會調用Sync類中的nonfairTryAcquire方法嘗試非公平鎖獲取資源;獲取失敗的話,AQS中的acquire()方法會調用addWaiter()方法將當前線程封裝成Node節(jié)點放入到等待隊列的隊尾,而后AQS中的acquire()方法會調用AQS中的acquireQueued()方法讓線程在等待隊列中排隊。這就是非公平鎖的整個流程。
到此這篇關于Java公平鎖與非公平鎖的核心原理講解的文章就介紹到這了,更多相關Java公平鎖 非公平鎖內容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關文章希望大家以后多多支持腳本之家!
相關文章
線程池之newCachedThreadPool可緩存線程池的實例
這篇文章主要介紹了線程池之newCachedThreadPool可緩存線程池的實例,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教2021-06-06Spring如何替換掉默認common-logging.jar
這篇文章主要介紹了Spring如何替換掉默認common-logging.jar,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友可以參考下2020-05-05解讀maven項目中Tomcat10與JSTL的問題匯總(Debug親身經歷)
這篇文章主要介紹了解讀maven項目中Tomcat10與JSTL的問題匯總(Debug親身經歷),具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教2023-07-07