鎖的可重入性驗(yàn)證
1. 前言
本節(jié)內(nèi)容主要是對(duì) Java 鎖的可重入性進(jìn)行驗(yàn)證,鎖的可重入性的設(shè)計(jì)是避免死鎖非常好的設(shè)計(jì)思想。本節(jié)內(nèi)容的知識(shí)點(diǎn)如下:
- 什么是鎖的可重入性,這是本節(jié)課程的基礎(chǔ)內(nèi)容;
- 了解可重入鎖與非可重入性鎖的不同之處,以凸顯可重入性鎖的優(yōu)勢(shì)所在,為本節(jié)基礎(chǔ)內(nèi)容;
- 了解什么情況下使用可重入鎖,是本節(jié)的重點(diǎn)內(nèi)容之一;
- synchronized 關(guān)鍵字驗(yàn)證鎖的可重入性試驗(yàn),為本節(jié)核心內(nèi)容之一;
- ReentrantLock 驗(yàn)證鎖的可重入性試驗(yàn),為本節(jié)核心內(nèi)容之一;
其實(shí) synchronized 關(guān)鍵字與 ReentrantLock 都是 Java 常見(jiàn)的可重入鎖,本節(jié)內(nèi)容使用 ReentrantLock 和 synchronized 來(lái)講解鎖的可重入性。
2. 什么是鎖的可重入性
定義:可重入鎖又名遞歸鎖,是指在同一個(gè)線(xiàn)程在外層方法獲取鎖的時(shí)候,再進(jìn)入該線(xiàn)程的內(nèi)層方法會(huì)自動(dòng)獲取鎖(前提鎖對(duì)象得是同一個(gè)對(duì)象或者 class),不會(huì)因?yàn)橹耙呀?jīng)獲取過(guò)還沒(méi)釋放而阻塞。
Java 中 ReentrantLock 和 synchronized 都是可重入鎖,可重入鎖的一個(gè)優(yōu)點(diǎn)是可一定程度避免死鎖。
可重入鎖原理:可重入鎖的原理是在鎖內(nèi)部維護(hù)一個(gè)線(xiàn)程標(biāo)示,用來(lái)標(biāo)示該鎖目前被哪個(gè)線(xiàn)程占用,然后關(guān)聯(lián)一個(gè)計(jì)數(shù)器。一開(kāi)始計(jì)數(shù)器值為 0,說(shuō)明該鎖沒(méi)有被任何線(xiàn)程占用。當(dāng)一個(gè)線(xiàn)程獲取了該鎖時(shí),計(jì)數(shù)器的值會(huì)變成 1,這時(shí)其他線(xiàn)程再來(lái)獲取該鎖時(shí)會(huì)發(fā)現(xiàn)鎖的所有者不是自己而被阻塞掛起。
但是當(dāng)獲取了該鎖的線(xiàn)程再次獲取鎖時(shí)發(fā)現(xiàn)鎖擁有者是自己,就會(huì)把計(jì)數(shù)器值加+1, 當(dāng)釋放鎖后計(jì)數(shù)器值-1。當(dāng)計(jì)數(shù)器值為 0 時(shí),鎖里面的線(xiàn)程標(biāo)示被重置為 null,這時(shí)候被阻塞的線(xiàn)程會(huì)被喚醒來(lái)競(jìng)爭(zhēng)獲取該鎖。
3. 可重入鎖與非可重入性鎖
Java 中 ReentrantLock 和 synchronized 都是可重入鎖,可重入鎖的一個(gè)優(yōu)點(diǎn)是可一定程度避免死鎖。
為了解釋可重入鎖與非可重入性鎖的區(qū)別與聯(lián)系,我們拿可重入鎖 ReentrantLock 和 非重入鎖 NonReentrantLock 進(jìn)行簡(jiǎn)單的分析對(duì)比。
相同點(diǎn): ReentrantLock 和 NonReentrantLock 都繼承父類(lèi) AQS,其父類(lèi) AQS 中維護(hù)了一個(gè)同步狀態(tài) status 來(lái)計(jì)數(shù)重入次數(shù),status 初始值為 0。
不同點(diǎn):當(dāng)線(xiàn)程嘗試獲取鎖時(shí),可重入鎖先嘗試獲取并更新 status 值,如果 status == 0 表示沒(méi)有其他線(xiàn)程在執(zhí)行同步代碼,則把 status 置為 1,當(dāng)前線(xiàn)程開(kāi)始執(zhí)行。
如果 status != 0,則判斷當(dāng)前線(xiàn)程是否是獲取到這個(gè)鎖的線(xiàn)程,如果是的話(huà)執(zhí)行 status+1,且當(dāng)前線(xiàn)程可以再次獲取鎖。
而非可重入鎖是直接去獲取并嘗試更新當(dāng)前 status 的值,如果 status != 0 的話(huà)會(huì)導(dǎo)致其獲取鎖失敗,當(dāng)前線(xiàn)程阻塞,導(dǎo)致死鎖發(fā)生。
4. 什么情況下使用可重入鎖
我們先來(lái)看看如下代碼:同步方法 helloB 方法調(diào)用了同步方法 helloA。
public class DemoTest{
public synchronized void helloA(){
System.out.println("helloA");
}
public synchronized void helloB(){
System.out.println("helloB");
helloA();
}
}
在如上代碼中,調(diào)用 helloB 方法前會(huì)先獲取內(nèi)置鎖,然后打印輸出。之后調(diào)用 helloA 方法,在調(diào)用前會(huì)先去獲取內(nèi)置鎖,如果內(nèi)置鎖不是可重入的,那么調(diào)用線(xiàn)程將會(huì)一直被阻塞。
因此,對(duì)于同步方法內(nèi)部調(diào)用另外一個(gè)同步方法的情況下,一定要使用可重入鎖,不然會(huì)導(dǎo)致死鎖的發(fā)生。
5. synchronized 驗(yàn)證鎖的可重入性
為了更好的理解 synchronized 驗(yàn)證鎖的可重入性,我們來(lái)設(shè)計(jì)一個(gè)簡(jiǎn)單的場(chǎng)景。
場(chǎng)景設(shè)計(jì):
- 創(chuàng)建一個(gè)類(lèi),該類(lèi)中有兩個(gè)方法,helloA 方法和 helloB 方法;
- 將兩個(gè)方法內(nèi)部的邏輯進(jìn)行 synchronized 同步;
- helloA 方法內(nèi)部調(diào)用 helloB 方法,營(yíng)造可重入鎖的場(chǎng)景;
- main 方法創(chuàng)建線(xiàn)程,調(diào)用 helloA 方法;
- 觀察結(jié)果,看是否可以成功進(jìn)行調(diào)用。
實(shí)例:
public class DemoTest {
public static void main(String[] args) {
new Thread(new SynchronizedTest()). start();
}
}
class SynchronizedTest implements Runnable {
private final Object obj = new Object();
public void helloA() { //方法1,調(diào)用方法2
synchronized (obj) {
System.out.println(Thread.currentThread().getName() + " helloA()");
helloB();
}
}
public void helloB() {
synchronized (obj) {
System.out.println(Thread.currentThread().getName() + " helloB()");
}
}
@Override
public void run() {
helloA(); //調(diào)用helloA方法
}
}
結(jié)果驗(yàn)證:
Thread-0 helloA()
Thread-0 helloB()
結(jié)果解析:如果同一線(xiàn)程,鎖不可重入的話(huà),helloB 需要等待 helloA 釋放 obj 鎖,如此一來(lái),helloB 無(wú)法進(jìn)行鎖的獲取,最終造成無(wú)限等待,無(wú)法正常執(zhí)行。此處說(shuō)明了 synchronized 關(guān)鍵字的可重入性,因此能夠正常進(jìn)行兩個(gè)方法的執(zhí)行。
6. ReentrantLock 驗(yàn)證鎖的可重入性
相同的場(chǎng)景,對(duì)代碼進(jìn)行如下改造,將 synchronized 同步代碼塊修改成 lock 接口同步,我們看代碼實(shí)例如下:
public class DemoTest {
public static void main(String[] args) {
new Thread(new SynchronizedTest()). start();
}
}
class SynchronizedTest implements Runnable {
private final Lock lock = new ReentrantLock();
public void helloA() { //方法1,調(diào)用方法2
lock.lock();
try {
System.out.println(Thread.currentThread().getName() + " helloA()");
helloB();
} finally {
lock.unlock();
}
}
public void helloB() {
lock.lock();
try {
System.out.println(Thread.currentThread().getName() + " helloB()");
} finally {
lock.unlock();
}
}
@Override
public void run() {
helloA();
}
}
結(jié)果驗(yàn)證:
Thread-0 helloA()
Thread-0 helloB()
結(jié)果解析:ReentrantLock 一樣是可重入鎖,試驗(yàn)成功。
7. 小結(jié)
鎖的可重入性這一概念對(duì)于并發(fā)編程非常重要,對(duì)于本節(jié)內(nèi)容需要深入的理解并掌握。我們之前已經(jīng)學(xué)習(xí)過(guò)了 synchronized 關(guān)鍵字和 ReentrantLock 鎖,此處知識(shí)用兩者進(jìn)行了可重入的驗(yàn)證。
本節(jié)關(guān)鍵點(diǎn)在于可重入性的意義所在,需要結(jié)合實(shí)例進(jìn)行更加細(xì)致的理解和掌握。