JDK鎖的基礎 AQS實現原理(二)

2021-08-18 11:29:27 字數 3797 閱讀 2764

上文介紹了aqs的一些基礎知識,包括clh鎖的原理和aqs的一些資料結構,這篇文章中我們來分析一下aqs的方法。aqs是乙個抽象類,定義了幾個模板方法交給子類去實現,分別是:

protected boolean tryacquire(int arg)protected boolean tryrelease(int arg)protected int tryacquireshared(int arg)protected boolean tryreleaseshared(int arg)

本文不分析獲取共享鎖的情況。

先來看一下比較簡單的乙個子類實現reentrantlock中的內部類fairsync,下面就直接分析一下reentrantlock的實現,reentrantlock有乙個字段private final sync sync;,而sync正是繼承自aqs。來看一下reentrantlock的建構函式:

public

reentrantlock()

public

reentrantlock(boolean fair)

其中nonfairsyncfairsync均繼承自sync類,區別只是獲取鎖的方式是否公平:

//nonfairsync

final void lock()

//fairsync

final void lock()

可以看出,非公平鎖在獲取鎖的時候會先嘗試一下是否能直接獲取鎖,如果可以,就不必進行排隊(acquire方法),而公平鎖會直接進入等待佇列排隊。

看一下aqs中acquire方法的實現:

public

final

void

acquire(int arg)

acquire方法大體的邏輯就是先呼叫tryacquire嘗試獲取鎖,如果失敗的話就新建乙個node物件並新增到aqs的等待佇列中去。

private node addwaiter(node mode) 

}//新增node失敗,呼叫enq方法無限迴圈地新增

enq(node);

return node;

}

addwaiter方法新建乙個node物件,並且設定成exclusive模式,也就是排它鎖。在該方法中,先使用原子操作嘗試把新建節點新增到等待佇列的隊尾,如果新增成功則直接返回,否則呼叫enq()方法新增。

private node enq(final node node)  else }}

}

在此要說明的是,aqs的等待佇列是使用lazy模式來初始化的,也就是說,如果一直沒有執行緒來競爭鎖,那麼等待佇列一直都不會建立。而等待佇列的初始化是在enq()方法中進行的。

由於在addwaiter的過程中可能其他執行緒已經釋放了鎖,所以在acquirequeued方法中把新建node阻塞之前可以再嘗試一下獲取鎖,如果仍然失敗,再阻塞該執行緒:

final

boolean acquirequeued(final node node, int arg)

if (shouldparkafte***iledacquire(p, node) &&

parkandcheckinterrupt())

interrupted = true;

}} finally

}

aqs等待佇列中的頭節點代表當前持有鎖的執行緒,所以當乙個節點的前驅節點是頭節點的時候,可以嘗試一下獲取鎖(因為可能鎖持有執行緒已經釋放了鎖),如果獲取鎖成功,那麼把該節點設定成頭節點並返回。如果當前節點的前驅節點並不是頭節點,那麼不給它嘗試獲取鎖的機會,因為aqs等待佇列是fifo的,此時沒有輪到它獲取鎖。

如果該節點嘗試獲取鎖失敗,那麼呼叫shouldparkafte***iledacquire方法判斷是否需要阻塞當前節點:

private

static boolean shouldparkafte***iledacquire(node pred, node node) while (pred.waitstatus > 0);

pred.next = node;

} else

return

false;

}

如果shouldparkafte***iledacquire方法返回false,那麼在acquirequeued方法中進行下一輪迴圈(因為shouldparkafte***iledacquire方法可能修改了當前節點的前驅節點或者前驅節點的狀態),此時當前節點的前驅節點可能變成頭節點或者前驅節點的狀態變成signal。如果shouldparkafte***iledacquire方法返回true,那麼就呼叫parkandcheckinterrupt方法阻塞當前節點代表的執行緒:

private

final

boolean

parkandcheckinterrupt()

至此,獲取鎖的acquire方法已經分析完畢。再來看看釋放鎖的release方法:

public

final

boolean

release(int arg)

return

false;

}

release方法呼叫tryrelease(模板方法,交給子類去實現)來釋放相應數量的訊號量,如果方法呼叫成功,那麼就喚醒等待佇列頭節點的後繼節點。

private

void

unparksuccessor(node node)

if (s != null)

locksupport.unpark(s.thread);

}

假設沒有中斷的情況下,在unparksuccessor方法中unpark的執行緒會繼續在方法acquirequeued方法的執行,並且其parkandcheckinterrupt方法會返回false,然後就會再次嘗試獲取鎖,此次獲取鎖就會成功了,然後acquire方法就會返回,代表該執行緒已經可以執行臨界區**了。

最後再來看一下取消等待的方法,在獲取鎖的時候如果設定了超時時間並且超時之後就需要取消等待,此時會轉到cancelacquire方法:

private

void

cancelacquire(node node) else else

node.next = node; // help gc

}}

JDK鎖的基礎 AQS實現原理(三)

本文主要來分析一下aqs共享模式鎖的獲取和釋放,aqs其實只是乙個框架,它主要提供了乙個int型別的state欄位,子類繼承時用於儲存子類的狀態,並且提供了乙個等待佇列以及維護等待佇列的方法。至於如何使用這個狀態值和等待佇列,就需要子類根據自己的需求來實現了。以semaphore類為例,semaph...

AQS共享鎖的實現原理

前面的文章lock的實現中分析了aqs獨佔鎖的實現原理,那麼接下來就分析下aqs是如何實現共享鎖的。共享鎖的介紹 共享鎖 同一時刻有多個執行緒能夠獲取到同步狀態。那麼它是如何做到讓多個執行緒獲取到同步狀態呢?來看一下獲取共享鎖的過程 1.執行緒呼叫aqs的acquireshared 申請獲取鎖 可有...

基於AQS的獨佔鎖實現邏輯

獨佔鎖的正常使用方式,先從加鎖邏輯開始。lock lock newreentrantlock public void test finally reentrantlock分公平鎖和非公平鎖,公平鎖指搶鎖的執行緒進來先入佇列排隊,按照fifo的方式獲取鎖。而非公平鎖指執行緒開始可以插隊嘗試獲取鎖,如果...