所謂重入鎖,指的是以線程為單位,當一個線程獲取對象鎖之后,這個線程可以再次獲取本對象上的鎖,而其他的線程是不可以的。
synchronized 和 ReentrantLock 都是可重入鎖。
可重入鎖的意義在於防止死鎖。
可重入鎖簡單演示
什么是 “可重入”,可重入就是說某個線程已經獲得某個鎖,可以再次獲取鎖而不會出現死鎖。
ReentrantLock 和 synchronized 不一樣,需要手動釋放鎖,所以使用 ReentrantLock的時候一定要手動釋放鎖,並且加鎖次數和釋放次數要一樣
例如
//演示可重入鎖是什么意思,可重入,就是可以重復獲取相同的鎖,synchronized和ReentrantLock都是可重入的 //可重入降低了編程復雜性 public class WhatReentrant { public static void main(String[] args) { // TODO Auto-generated method stub new Thread(new Runnable() { @Override public void run() { synchronized (this) { System.out.println("第1次獲取鎖,這個鎖是:" + this); int index = 1; while (true) { synchronized (this) { System.out.println("第" + (++index) + "次獲取鎖,這個鎖是:" + this); } if (index == 10) { break; } } } } }).start(); } }
//演示可重入鎖是什么意思 public class WhatReentrant2 { public static void main(String[] args) { ReentrantLock lock = new ReentrantLock(); new Thread(new Runnable() { @Override public void run() { try { lock.lock(); System.out.println("第1次獲取鎖,這個鎖是:" + lock); int index = 1; while (true) { try { lock.lock(); System.out.println("第" + (++index) + "次獲取鎖,這個鎖是:" + lock); try { Thread.sleep(new Random().nextInt(200)); } catch (InterruptedException e) { e.printStackTrace(); } if (index == 10) { break; } } finally { lock.unlock(); } } } finally { lock.unlock(); } } }).start(); } }
可以發現沒發生死鎖,可以多次獲取相同的鎖
可重入鎖有
- synchronized
- ReentrantLock
使用ReentrantLock的注意點
ReentrantLock 和 synchronized 不一樣,需要手動釋放鎖,所以使用 ReentrantLock的時候一定要手動釋放鎖,並且加鎖次數和釋放次數要一樣
以下代碼演示,加鎖和釋放次數不一樣導致的死鎖
public class WhatReentrant3 { public static void main(String[] args) { ReentrantLock lock = new ReentrantLock(); new Thread(new Runnable() { @Override public void run() { try { lock.lock(); System.out.println("第1次獲取鎖,這個鎖是:" + lock); int index = 1; while (true) { try { lock.lock(); System.out.println("第" + (++index) + "次獲取鎖,這個鎖是:" + lock); try { Thread.sleep(new Random().nextInt(200)); } catch (InterruptedException e) { e.printStackTrace(); } if (index == 10) { break; } } finally { // lock.unlock();// 這里故意注釋,實現加鎖次數和釋放次數不一樣 } } } finally { lock.unlock(); } } }).start(); new Thread(new Runnable() { @Override public void run() { try { lock.lock(); for (int i = 0; i < 20; i++) { System.out.println("threadName:" + Thread.currentThread().getName()); try { Thread.sleep(new Random().nextInt(200)); } catch (InterruptedException e) { e.printStackTrace(); } } } finally { lock.unlock(); } } }).start(); } }
可以看出,由於加鎖次數和釋放次數不一樣,第二個線程始終無法獲取到鎖,導致一直在等待。
稍微改一下,在外層的finally里頭釋放9次,讓加鎖和釋放次數一樣,就沒問題了
try { lock.lock(); System.out.println("第1次獲取鎖,這個鎖是:" + lock); int index = 1; while (true) { try { lock.lock(); System.out.println("第" + (++index) + "次獲取鎖,這個鎖是:" + lock); ... 代碼省略節省篇幅... } finally { // lock.unlock();// 這里故意注釋,實現加鎖次數和釋放次數不一樣 } } } finally { lock.unlock(); // 在外層的finally里頭釋放9次,讓加鎖和釋放次數一樣,就沒問題了 for (int i = 0; i < 9; i++) { lock.unlock(); } }
實現原理
實現原理是通過為每個鎖關聯一個請求計數器和一個占有它的線程。
當計數為0時,認為鎖是未被占有的;線程請求一個未被占有的鎖時,JVM將記錄鎖的占有者,並且將請求計數器置為1 。
如果同一個線程再次請求這個鎖,計數將遞增;
每次占用線程退出同步塊,計數器值將遞減。直到計數器為0,鎖被釋放。
關於父類和子類的鎖的重入:子類覆寫了父類的synchonized方法,然后調用父類中的方法,此時如果沒有重入的鎖,那么這段代碼將產生死鎖(很好理解吧)。
例子:
比如說A類中有個方法public synchronized methodA1(){
methodA2();
}
而且public synchronized methodA2(){
//具體操作
}
也是A類中的同步方法,當當前線程調用A類的對象methodA1同步方法,如果其他線程沒有獲取A類的對象鎖,那么當前線程就獲得當前A類對象的鎖,
然后執行methodA1同步方法,方法體中調用methodA2同步方法,當前線程能夠再次獲取A類對象的鎖,而其他線程是不可以的,這就是可重入鎖。
代碼演示:
不可重入鎖:
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 Count{ Lock lock = new Lock(); public void print(){ lock.lock(); doAdd(); lock.unlock(); } public void doAdd(){ lock.lock(); //do something lock.unlock(); } }
當前線程執行print()方法首先獲取lock,接下來執行doAdd()方法就無法執行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(); } } } }
所謂可重入,意味着線程可以進入它已經擁有的鎖的同步代碼塊兒。
我們設計兩個線程調用print()方法,第一個線程調用print()方法獲取鎖,進入lock()方法,
由於初始lockedBy是null,所以不會進入while掛起當前線程,而是是增量lockedCount並記錄lockBy為第一個線程。
接着第一個線程進入doAdd()方法,由於是同一線程,所以不會進入while掛起線程,接着增量lockedCount,
當第二個線程嘗試lock,由於isLocked=true,所以他不會獲取該鎖,直到第一個線程調用兩次unlock()將lockCount遞減為0,才將標記為isLocked設置為false。
可重入鎖的概念和設計思想大體如此,Java中的可重入鎖ReentrantLock設計思路也是這樣。
synchronized和ReentrantLock 都是可重入鎖。
ReentrantLock與synchronized比較:
- 前者使用靈活,但是必須手動開啟和釋放鎖
- 前者擴展性好,有時間鎖等候(tryLock( )),可中斷鎖等候(lockInterruptibly( )),鎖投票等,適合用於高度競爭鎖和多個條件變量的地方
- 前者提供了可輪詢的鎖請求,可以嘗試去獲取鎖(tryLock( )),如果失敗,則會釋放已經獲得的鎖。有完善的錯誤恢復機制,可以避免死鎖的發生。
https://blog.csdn.net/w8y56f/article/details/89554060
https://blog.csdn.net/qq_39101581/article/details/82144499