Lock鎖底層依賴於AQS實現,AQS提供了多種鎖的實現模式,其中獨佔鎖和共享鎖是主要的兩種模式。AQS本身是一種模板方法設計模式,即AQS對外部提供了一些模板方法,而這些模板方法又會呼叫由子類實現的抽象方法。今天我們主要是比較AQS中共享鎖和獨佔鎖的底層實現方面的不同。
public final void acquire(int arg){/*對外提供的獨佔鎖的模板方法*/ public final void acquireShared(int arg){ //對外提供的共享鎖的模板方式
if(!tryAcquire(arg) if(tryAcquireShared(arg)<0)
&&acquireQueued(addWaiter(Node.EXCLUSIVE),arg)) doAcquireShared(arg);
selfInterrupt()/*中斷當前呼叫執行緒*/ }
}
先來分析acuqire(arg)方法,首先我們要理解java中的短路運算子&&,也就是說當tryAcquire(arg)方法返回false時,即獲取鎖失敗時,才會執行acquireQueued(addWaiter(Node.EXCLUSIVE),arg),剖開語句acquireQueued(**),先執行addWaiter(Node.EXCLUSIVE),然後執行acquireQueued(),所以一句if基本上就呼叫了所有的後續處理,這種編碼方式,在java原始碼實現中非常常見。相比之下,acquireShared(arg)方法更加符合我們平時的編碼習慣。
addWaiter方法的目的是將未成功獲取到鎖的執行緒中加入到同步佇列中去,先看原始碼:
private Node addWaiter(Node mode){ private Node enq(final Node node){
Node node=new Node(Thread.currentThread(),mode); for(;;){
Node pred=tail; Node t=tail;
if(pred!=null){ if(t==null){
node.prev=pred; if(compareAndSetHead(new Node()))
if(compareAndSetTail(pred,node)){/*注意該方式是原子方式*/ tail=head;
pred.next=node; }else{
return node; node.prev=t;
} if(compareAndSetTail(t,node)){
} t.next=node;
enq(node); return t;
return node; }
} }
}
}
上述的addWaiter方法首先構造一個新的節點,並先嘗試插入同步佇列,如果成功後,直接返回,如果不成功,則呼叫enq方法進行迴圈插入。節點既然已經被加入到同步佇列中去了,那麼接下來就需要將執行緒阻塞,阻塞之前需要再次嘗試獲取鎖,如果仍然失敗則阻塞,具體的處理方法在acquireQueued(node,arg);
final boolean acquireQueued(final Node node,int arg){
boolean failed=true;
try{
boolean interrupted=false;
for(;;){
final Node p=node.predecessor();
if(p==head&&tryAcquire(arg)){
setHead(node);//注意這一段程式碼並沒有進行併發控制,因為這一句是由獲取鎖的執行緒設定,所以不需要進行同步控制
p.next=null;
failed=false;
return interrupted;
}
if(shouldParkAfterFailedAcquire(p,node)&&parkAndCheckInterrupt())
interrupted=true;
}
}finally{
if(failed)
cancelAcquire(node);
}
}
在上述程式碼中,關鍵的一點是shouParkAfterFailedAcquire方法和parkAndCheckInterrupt方法,接下來我們看下這兩個函式的原始碼實現:
private static boolean shouldParkAfterFailedAcquire(Node pred,Node node){
int ws=pred.waitStatus;
if(ws==Node.SIGNAL) return true;// SIGNAL表示該節點的後繼節點正在阻塞中,當該節點釋放時,將喚醒後繼節點。此時node可以安全地進行阻塞,因為可以保證會被喚醒
if(ws>0){//表示前置節點已經被取消
do{//迴圈找到一個未被取消的節點
node.prev=pred=pred.prev;
}while(pred.waitStatus>0);
pred.next=node; //執行到這一句時,acquireQueued方法會迴圈一次,再次嘗試獲取鎖
}else{
compareAndSetWaitStatus(pred,ws,Node.SIGNAL);
}
return false;
}
規則1:如果前繼的節點狀態為SIGNAL,表明當前節點可以安全地進行阻塞,則返回成功,此時acquireQueued方法的第12行(parkAndCheckInterrupt)將導致執行緒阻塞
規則2:如果前繼節點狀態為CANCELLED(ws>0),說明前置節點已經被放棄,則回溯到一個非取消的前繼節點,返回false,acquireQueued方法的無限迴圈將遞迴呼叫該方法,直至規則1返回true,導致執行緒阻塞
規則3:如果前繼節點狀態為非SIGNAL、非CANCELLED,則設定前繼的狀態為SIGNAL,返回false後進入acquireQueued的無限迴圈,與規則2同
下面我們再來分析一下,共享鎖acquireShared()方法中的doAcquireShared(arg),呼叫該方法說明,共享鎖已經用完了,當前執行緒需要進行等待重新獲取:
private void doAcquireShared(int arg){
final Node node=addWaiter(Node.SHARED);//構造一個新的節點,並將新的節點加入到同步佇列中
boolean failed=true;
try{
boolean interrupted=false;
for(;;){
final Node p=node.predecessor();
if(p==head){
int r=tryAcquireShared(arg);//再次嘗試獲取共享鎖
if(r>=0){
setHeadAndPropagate(node,r);//這一句很關鍵
p.next=null;
if(interrupted) selfInterrupt();
failed=false;
return;
}
if(shouldParkAfterFailedAcquire(p,node)&&parkAndCheckInterrupt())//同獨佔鎖的規則一樣
interrupted=true;
}
}
}finally{
if(failed)
cancelAcquire(node);
}
}
上面的程式碼中主要的一句關鍵程式碼是setHeadAndPropagate方法,主要能夠呼叫setHeadAndPropagate方法,說明當前執行緒已經活到了鎖,下面我們來看看這句程式碼的實現:
private void setHeadAndPropagate(Node node,int propagate){
Node h=head;
setHead(node);//因為有多個執行緒可能同時獲取了共享鎖,setHead方法可能會設定不成功,不過已經獲取了鎖,也不用關心是否設定成功
if(propagate>0||h==null||h.waitStatus<0){
Node s=node.next;
if(s==null||s.isShared())
doReleaseShared();
}
}
獨佔鎖某個節點被喚醒之後,它只需要將這個節點設定成head就完事了,而共享鎖不一樣,某個節點被設定為head之後,如果它的後繼節點是SHARED狀態的,那麼將繼續通過doReleaseShared方法嘗試往後喚醒節點,實現了共享狀態的向後傳播。