一篇文章讓你徹底瞭解Java可重入鎖和不可重入鎖
可重入鎖
廣義上的可重入鎖指的是可重復可遞歸調用的鎖,在外層使用鎖之後,在內層仍然可以使用,並且不發生死鎖(前提得是同一個對象或者class),這樣的鎖就叫做可重入鎖。
我的理解就是,某個線程已經獲得某個鎖,可以無需等待而再次獲取鎖,並且不會出現死鎖(不同線程當然不能多次獲得鎖,需要等待)。
簡單的說,就是某個線程獲得某個鎖,之後可以不用等待而再次獲取鎖且不會出現死鎖。
常見的可重入鎖
Synchronized和ReentrantLock 都是可重入鎖。
可重入鎖的釋放
同一個線程獲取同一個鎖,狀態值state會累加,假設state累加到瞭2,每釋放一次鎖會減1,隻有當狀態值state減到0瞭,其他線程才有機會獲取鎖。也就是說,state歸零才是已釋放鎖的標致。
可重入鎖示例
public class ReentrantTest implements Runnable { @Override public void run() { get(); } public synchronized void get() { System.out.println(Thread.currentThread().getName()); set(); } /** * 遞歸方法 */ public synchronized void set() { System.out.println(Thread.currentThread().getName()); } /** * 這裡的對象鎖隻有一個,就是rt對象的鎖。 * 當執行rt的get方法時,該線程獲得rt對象的鎖。在get方法內執行set方法時再次請求rt對象的鎖,因為synchronized是可重入鎖,所以又可以得到該鎖。循環這個過程。 * 假設不是可重入鎖的話,那麼請求的過程中會出現阻塞,從而導致死鎖。 * @param args */ public static void main(String[] args) { ReentrantTest rt = new ReentrantTest(); // for(;;)模擬無限循環 for(;;){ new Thread(rt).start(); } } }
分析:這裡的對象鎖隻有一個,就是rt對象的鎖。當執行rt的get方法時,該線程獲得rt對象的鎖。在get方法內執行set方法時再次請求rt對象的鎖,因為synchronized是可重入鎖,所以又可以得到該鎖。循環這個過程。假設不是可重入鎖的話,那麼請求的過程中會出現阻塞,從而導致死鎖。
死鎖
多線程中,不同的線程都在等待其它線程釋放鎖,而其它線程由於一些原因遲遲沒有釋放鎖。程序的運行處於阻塞狀態,不能正常運行也不能正常終止。
運行結果
set()和get()同時輸出瞭相同的線程名稱,也就是說某個線程執行的時候,不僅進入瞭set同步方法,還進入瞭get同步方法。遞歸使用synchronized也沒有發生死鎖,證明其是可重入的。
可重入鎖的實現原理?
每一個鎖關聯一個線程持有者和計數器,當計數器為 0 時表示該鎖沒有被任何線程持有,那麼任何線程都可能獲得該鎖而調用相應的方法;當某一線程請求成功後,JVM會記下鎖的持有線程,並且將計數器置為 1;此時其它線程請求該鎖,則必須等待;而該持有鎖的線程如果再次請求這個鎖,就可以再次拿到這個鎖,同時計數器會遞增1;當線程退出同步代碼塊時,計數器會遞減1,如果計數器為 0,則釋放該鎖。
再分析一下上面可重入鎖的例子
遞歸調用一次同步代碼塊,計數器會變為2,整個遞歸調用執行完,先退出內層執行減1,再退出外層執行減1。然後釋放鎖。
不可重入鎖
就是某個線程已經獲得某個鎖,之後不可以再次獲取鎖,會被阻塞。
設計一個不可重入鎖
public class Lock { private boolean isLocked = false; /** * 加鎖 */ public synchronized void lock() throws Exception{ while(isLocked){ //當前線程釋放鎖,讓出CPU,進入等待狀態,直到被喚醒,才繼續執行15行 wait(); System.out.println("wait"); } isLocked = true; } /** * 解鎖 */ public synchronized void unlock(){ isLocked = false; //喚醒一個等待的線程繼續執行 notify(); } }
測試
public class Test { Lock lock = new Lock(); public void print() throws Exception{ //加鎖 標記為true lock.lock(); //釋放鎖->等待 阻塞在16行 doAdd(); lock.unlock(); } public void doAdd() throws Exception{ lock.lock(); System.out.println("doAdd"); lock.unlock(); } public static void main(String[] args)throws Exception { Test test=new Test(); test.print(); } }
結果:這裡,雖然模擬的是不可重入鎖,實際還是在單線程環境中的。當前線程執行print()方法首先加鎖 標記為true,接下來釋放鎖->等待 阻塞在16行內部的14行。整個過程中,第一次進入lock同步方法,執行完畢,第二次進入lock同步方法,阻塞等待。這個例子很好的說明瞭不可重入鎖。
到此這篇關於一篇文章讓你徹底瞭解Java可重入鎖和不可重入鎖的文章就介紹到這瞭,更多相關Java可重入鎖和不可重入鎖內容請搜索WalkonNet以前的文章或繼續瀏覽下面的相關文章希望大傢以後多多支持WalkonNet!