首先引入概念:
可重入鎖:廣義上的可重入鎖指的是可重復可遞歸調用的鎖,在外層使用鎖之后,在內層仍然可以使用,並且不發生死鎖(前提得是同一個對象或者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; Thread lockedBy = 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方法,喚醒其他的線程,后續線程搶奪資源拿到鎖之后,即可實現同步安全的執行。
總結如下:
可重入鎖,也叫做遞歸鎖,指的是同一線程 外層函數獲得鎖之后 ,內層遞歸函數仍然有獲取該鎖的代碼,但不受影響。
不可重入鎖,也可以叫非遞歸鎖,就是拿不到鎖的情況會不停自旋循環檢測來等待,不進入內核態沉睡,而是在用戶態自旋嘗試。
同一個線程可以多次獲取同一個遞歸鎖,不會產生死鎖。而如果一個線程多次獲取同一個非遞歸鎖,則會產生死鎖。