上文介紹了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)
其中nonfairsync
和fairsync
均繼承自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的方式獲取鎖。而非公平鎖指執行緒開始可以插隊嘗試獲取鎖,如果...