Java多執行緒/併發10、不可重入鎖/自旋鎖、可重入鎖

唐大麥發表於2017-04-28

鎖分為可重入鎖和不可重入鎖。
可重入和不可重入的概念是這樣的:當一個執行緒獲得了當前例項的鎖,並進入方法A,這個執行緒在沒有釋放這把鎖的時候,能否再次進入方法A呢?

  • 可重入鎖:可以再次進入方法A,就是說在釋放鎖前此執行緒可以再次進入方法A(方法A遞迴)。
  • 不可重入鎖(自旋鎖):不可以再次進入方法A,也就是說獲得鎖進入方法A是此執行緒在釋放鎖錢唯一的一次進入方法A。

先舉例來說明鎖的可重入性:

public class UnReentrant{
    Lock lock = new Lock();
    public void outer(){
        lock.lock();
        inner();
        lock.unlock();
    }
    public void inner(){
        lock.lock();
        //do something
        lock.unlock();
    }
}

outer中呼叫了inner,outer先鎖住了lock,這樣inner就不能再獲取lock。其實呼叫outer的執行緒已經獲取了lock鎖,但是不能在inner中重複利用已經獲取的鎖資源,這種鎖即稱之為 不可重入 。通常也稱為 自旋鎖 。相對來說,可重入就意味著:執行緒可以進入任何一個它已經擁有的鎖所同步著的程式碼塊。

不可重入鎖基本原理:

可以看到,當isLocked被設定為true後,線上程呼叫unlock()解鎖之前不管執行緒是否已經獲得鎖,都只能wait()。
程式碼如下:

public class Lock{  
    private boolean isLocked = false;  
    public synchronized void lock()  
        throws InterruptedException{  
        while(isLocked){  
            wait();  
        }  
        isLocked = true;  
    }  

    public synchronized void unlock(){  
        isLocked = false;  
        notify();  
    }  
}  

可重入鎖的基本原理

我們修改Lock,加入一個變數lockBy用來儲存已經獲得鎖的執行緒,這樣就能對有鎖的執行緒放行。

public class Lock{
    boolean isLocked = false;
    Thread  lockedBy = null;
    int lockedCount = 0;
    public synchronized void lock() throws InterruptedException{
        Thread callingThread = Thread.currentThread();
        while(isLocked && lockedBy != callingThread){
            wait();
        }
        isLocked = true;
        lockedCount++;
        lockedBy = callingThread;
    }
    public synchronized void unlock(){
        if(Thread.curentThread() == this.lockedBy){
            lockedCount--;
            if(lockedCount == 0){
                isLocked = false;
                notify();
            }
        }
    }
}

解釋一下程式中的兩個變數:
lockBy:儲存已經獲得鎖例項的執行緒,在lock()判斷呼叫lock的執行緒是否已經獲得當前鎖例項,如果已經獲得鎖,則直接跳過while,無需等待。
lockCount:記錄同一個執行緒重複對一個鎖物件加鎖的次數。否則,一次unlock就會解除所有鎖,即使這個鎖例項已經加鎖多次了。

在java 中,synchronized和java.util.concurrent.locks.ReentrantLock是可重入鎖。

相關文章