可重入鎖和不可重入鎖
首先引入概念:
可重入鎖:廣義上的可重入鎖指的是可重複可遞迴呼叫的鎖,在外層使用鎖之後,在內層仍然可以使用,並且不發生死鎖(前提得是同一個物件或者class),這樣的鎖就叫做可重入鎖,
java裡面最常見的鎖,ReentrantLock和synchronized都是可重入鎖
不可重入鎖:不可重入鎖,與可重入鎖相反,不可遞迴呼叫,遞迴呼叫就發生死鎖。即若當前執行緒執行某個方法已經獲取了該鎖,那麼在方法中嘗試再次獲取鎖時,就會獲取不到被阻塞。
如下圖設計一個不可重入鎖。
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(); } }
public class Test{ Lock lock = new Lock(); /** 呼叫列印的方法 */ public void print(){ lock.lock(); doAdd(); lock.unlock(); } public void doAdd(){ lock.lock(); //sout("執行業務程式碼") lock.unlock(); } }
場景說明:假設某業務下需要呼叫Test類裡面的print()方法,假設他的執行緒命名為T0,這時T0會執行lock.lock()方法,首先對於這個物件來說,isLocked屬性的初始值時false,因此它進入while迴圈的時候
判斷為false,直接跳出當前迴圈,把物件的isLocked屬性變為true,相當於拿到了鎖,這時T0再去執行doAdd(),由於要保證原子性,因此在doAdd方法裡面也加入了lock鎖,這時,執行緒還是T0執行緒,但
由於isLocked屬性由於第一次加鎖已經變成true,因此,T0執行緒執行到了wait()方法就處於等待,導致doAdd裡面的業務程式碼無法執行,導致執行緒阻塞。
下面我們來建立一個可重入鎖
public class Lock{ boolean isLocked = false; ThreadlockedBy = null; int lockedCount = 0; public synchronized void lock() throws InterruptedException{ Thread thread = Thread.currentThread(); while(isLocked && lockedBy != thread){ wait(); } isLocked = true; lockedCount++; lockedBy = thread; } public synchronized void unlock(){ if(Thread.currentThread() == this.lockedBy){ lockedCount--; if(lockedCount == 0){ isLocked = false; notify(); } } } }
public class Test{ Lock lock = new Lock(); /** 呼叫列印的方法 */ public void print(){ lock.lock(); doAdd(); lock.unlock(); } public void doAdd(){ lock.lock(); //sout("執行業務程式碼") lock.unlock(); } }
場景如上描述,假設執行緒T0進來了,呼叫print方法,lock.lock(),第一步首先拿到當前執行緒,由於初始的islocked為false,同時lockedby為null 和當前執行緒T0不相等,false &&true 得到還是false ,因此直接跳出while迴圈,執行緒不等待,將isLocked設定為true,同時設定當前鎖的數量從0加上1變成1,並且設定lockby為當前執行緒T0,此時T0繼續執行doAdd方法,當執行doAdd()裡面的lock.lock()時,同樣還是執行緒T0,因此while迴圈的判斷變成了true&& false,最終拿到的還是false,這時執行緒還是不等待,isLocked還是true,同時當前執行緒擁有的鎖變成了2,lockedby還是T0,這時假設又有T1,T2執行緒進來,當他們執行print()方法,執行到了lock.lock(),首先拿到當前執行緒是T1,而lockedby是T0,while迴圈的條件判斷是true&&true,則T1就處於了等待狀態,只有當T0執行完doAll()的業務程式碼,並第一次釋放鎖,lock.unlock(),當前執行緒的計數器減去1,這時T0再去執行print方法裡面的lock.unlock(),這時執行緒T0,計數器變數變成了0,同時設定isLocked為false,執行notify方法,喚醒其他的執行緒,後續執行緒搶奪資源拿到鎖之後,即可實現同步安全的執行。
總結如下:
可重入鎖,也叫做遞迴鎖,指的是同一執行緒 外層函式獲得鎖之後 ,內層遞迴函式仍然有獲取該鎖的程式碼,但不受影響。
不可重入鎖,也可以叫非遞迴鎖,就是拿不到鎖的情況會不停自旋迴圈檢測來等待,不進入核心態沉睡,而是在使用者態自旋嘗試。
同一個執行緒可以多次獲取同一個遞迴鎖,不會產生死鎖。而如果一個執行緒多次獲取同一個非遞迴鎖,則會產生死鎖。