1. 程式人生 > 其它 >深入瞭解ReentrantLock中的公平鎖和非公平鎖的加鎖機制

深入瞭解ReentrantLock中的公平鎖和非公平鎖的加鎖機制

ReentrantLock和synchronized一樣都是實現執行緒同步,但是像比synchronized它更加靈活、強大、增加了輪詢、超時、中斷等高階功能,可以更加精細化的控制執行緒同步,它是基於AQS實現的鎖,他支援公平鎖和非公平鎖,同時他也是可重入鎖和自旋鎖。

本章將基於原始碼來探索一下ReentrantLock的加鎖機制,文中如果存在理解不到位的地方,還請提出寶貴意見共同探討,不吝賜教。

公平鎖和非公平鎖的加鎖機制流程圖:

一、ReentrantLock的公平鎖

使用ReentrantLock的公平鎖,呼叫lock進行加鎖,lock方法的原始碼如下:

finalvoidlock(){
acquire(1);
}

publicfinalvoidacquire(intarg){
if(!tryAcquire(arg)&&
acquireQueued(addWaiter(Node.EXCLUSIVE),arg))
selfInterrupt();
}

可以看到,FairLock首先呼叫了tryAcquire,tryAcquire原始碼如下:

/**
*FairversionoftryAcquire.Don'tgrantaccessunless
*recursivecallornowaitersorisfirst.
*/
protectedfinalbooleantryAcquire(intacquires){
finalThreadcurrent=Thread.currentThread();
intc=getState();
if(c==0){
//如果佇列中不存在等待的執行緒或者當前執行緒在佇列頭部,則基於CAS進行加鎖
if(!hasQueuedPredecessors()&&
compareAndSetState(0,acquires)){
setExclusiveOwnerThread(current);
returntrue;
}
}
//是否可以進行鎖重入
elseif(current==getExclusiveOwnerThread()){
intnextc=c+acquires;
if(nextc<0)
thrownewError("Maximumlockcountexceeded");
setState(nextc);
returntrue;
}
returnfalse;
}

從原始碼中可以看到,當state為0,即沒有執行緒獲取到鎖時,FairLock首先會呼叫hasQueuedPredecessors()方法檢查佇列中是否有等待的執行緒或者自己是否在佇列頭部,如果佇列中不存在等待的執行緒或者自己在佇列頭部則呼叫compareAndSetState()方法基於CAS操作進行加鎖,如果CAS操作成功,則呼叫setExclusiveOwnerThread設定加鎖執行緒為當前執行緒。

當state不為0,即有執行緒佔用鎖的時候會判斷佔有鎖的執行緒是否是當前執行緒,如果是的話則可以直接獲取到鎖,這就是ReentrantLock是可重入鎖的體現。

如果通過呼叫tryAcquire沒有獲取到鎖,從原始碼中我們可以看到,FairLock會呼叫addWaiter()

方法將當前執行緒加入CLH佇列中,addWaiter方法原始碼如下:

privateNodeaddWaiter(Nodemode){
Nodenode=newNode(Thread.currentThread(),mode);
//Trythefastpathofenq;backuptofullenqonfailure
Nodepred=tail;
if(pred!=null){
node.prev=pred;
//基於CAS將當前執行緒節點加入佇列尾部
if(compareAndSetTail(pred,node)){
pred.next=node;
returnnode;
}
}
//如果CAS操作失敗,則呼叫enq自旋加入佇列
enq(node);
returnnode;
}

privateNodeenq(finalNodenode){
for(;;){
Nodet=tail;
if(t==null){//Mustinitialize
if(compareAndSetHead(newNode()))
tail=head;
}else{
node.prev=t;
if(compareAndSetTail(t,node)){
t.next=node;
returnt;
}
}
}
}

在addWaiter方法中,會CAS操作將當前執行緒節點加入佇列尾部,如果第一次CAS失敗,則會呼叫enq方法通過自旋的方式,多次嘗試進行CAS操作將當前執行緒加入佇列。

將當前執行緒加入佇列之後,會呼叫acquireQueued方法實現當前執行緒的自旋加鎖,acquireQueued原始碼如下:

finalbooleanacquireQueued(finalNodenode,intarg){
booleanfailed=true;
try{
booleaninterrupted=false;
for(;;){
finalNodep=node.predecessor();
if(p==head&&tryAcquire(arg)){
setHead(node);
p.next=null;//helpGC
failed=false;
returninterrupted;
}
if(shouldParkAfterFailedAcquire(p,node)&&
parkAndCheckInterrupt())
interrupted=true;
}
}finally{
if(failed)
cancelAcquire(node);
}
}

在acquireQueued方法中每次自旋首先會呼叫predecessor()方法獲取,當前執行緒節點的前節點,如果發現前節點是head節點,則說明當前執行緒節點處於對頭(head是傀儡節點),那麼則呼叫tryAcquire盡心加鎖。

如果當前執行緒節點不在佇列頭部,那麼則會呼叫shouldParkAfterFailedAcquire方法判斷當前執行緒節點是否可以掛起知道前節點釋放鎖時喚醒自己,如果可以掛起,則呼叫parkAndCheckInterrupt實現掛起操作。

shouldParkAfterFailedAcquire原始碼如下:

privatestaticbooleanshouldParkAfterFailedAcquire(Nodepred,Nodenode){
intws=pred.waitStatus;
if(ws==Node.SIGNAL)
/*
*Thisnodehasalreadysetstatusaskingarelease
*tosignalit,soitcansafelypark.
*/
returntrue;
if(ws>0){
/*
*Predecessorwascancelled.Skipoverpredecessorsand
*indicateretry.
*/
do{
node.prev=pred=pred.prev;
}while(pred.waitStatus>0);
pred.next=node;
}else{
/*
*waitStatusmustbe0orPROPAGATE.Indicatethatwe
*needasignal,butdon'tparkyet.Callerwillneedto
*retrytomakesureitcannotacquirebeforeparking.
*/
compareAndSetWaitStatus(pred,ws,Node.SIGNAL);
}
returnfalse;
}

shouldParkAfterFailedAcquire原始碼中,如果當前執行緒節點的前節點的waitStatus狀態為SIGNAL(-1)時,表明前節點已經設定了釋放鎖時喚醒(unpark)它的後節點,那麼當前執行緒節點可以安心阻塞(park),等待它的前節點在unlock時喚醒自己繼續嘗試加鎖。

如果前節點的waitStatus狀態>0,即為CANCELLED (1),表明前節點已經放棄了獲取鎖,那麼則會繼續往前找,找到一個能夠在unlock時喚醒自己的執行緒節點為止。如果前節點waitStatus狀態是CONDITION (-2),即處於等待條件的狀態,則會基於CAS嘗試設定前節點狀態為SIGNAL(主動干預前節點達到喚醒自己的目的)。

parkAndCheckInterrupt原始碼:

privatefinalbooleanparkAndCheckInterrupt(){
LockSupport.park(this);
returnThread.interrupted();
}

二、ReentrantLock的非公平鎖

和公平鎖加鎖機制不同的是,非公平鎖一上來不管佇列中是否還存線上程,就直接使用CAS操作進行嘗試加鎖(這就是它的非公平的體現),原始碼如下:

finalvoidlock(){
if(compareAndSetState(0,1))
setExclusiveOwnerThread(Thread.currentThread());
else
acquire(1);
}

publicfinalvoidacquire(intarg){
if(!tryAcquire(arg)&&
acquireQueued(addWaiter(Node.EXCLUSIVE),arg))
selfInterrupt();
}

如果CAS操作失敗(一上來就吃了個閉門羹),則呼叫acquire方法進行後續的嘗試和等待。從原始碼中可以看到,首先回呼叫tryAcquire方法進行再次嘗試加鎖或者鎖重入,NoFairLockd的tryAcquire方法原始碼如下:

finalbooleannonfairTryAcquire(intacquires){
finalThreadcurrent=Thread.currentThread();
intc=getState();
if(c==0){
if(compareAndSetState(0,acquires)){
setExclusiveOwnerThread(current);
returntrue;
}
}
elseif(current==getExclusiveOwnerThread()){
intnextc=c+acquires;
if(nextc<0)//overflow
thrownewError("Maximumlockcountexceeded");
setState(nextc);
returntrue;
}
returnfalse;
}

可以看到NoFairLock的tryAcquire方法和FairLock的tryAcquire方法唯一不同之處是NoFairLock中嘗試加鎖前不需要呼叫hasQueuedPredecessors方法判斷佇列中是否存在其他執行緒,而是直接進行CAS操作加鎖。

那麼如果再次嘗試加鎖或者鎖重入失敗,則會進行後續的和公平鎖完全一樣的操作流程(不再贅述),即:加入佇列(addWaiter)–>自旋加鎖(acquireQueued)。另外,關注Java知音公眾號,回覆“後端面試”,送你一份面試題寶典!

三、unlock解鎖

說完了公平鎖和非公平鎖的加鎖機制,我們再順帶簡單的看看解鎖原始碼。unlock原始碼如下:

publicvoidunlock(){
sync.release(1);
}

publicfinalbooleanrelease(intarg){
//嘗試釋放鎖
if(tryRelease(arg)){
Nodeh=head;
//鎖釋放成後喚醒後邊阻塞的執行緒節點
if(h!=null&&h.waitStatus!=0)
unparkSuccessor(h);
returntrue;
}
returnfalse;
}

總結 本文主要探索了公平鎖和非公平鎖的加鎖流程,他們獲取鎖的不同點和相同點。整篇文章涉及到了以下幾點:

  1. 公平鎖、非公平鎖加鎖過程
  2. 自旋鎖的實現以及自旋過程中的阻塞喚醒
  3. 可重入鎖的實現
  4. CLH佇列

轉載:blog.csdn.net/qq_40400960/article/details/114242448