并發(fā)鎖之 Lock 接口
1. 前言
本節(jié)內(nèi)容主要是對(duì) Java 并發(fā)鎖之 Lock 接口進(jìn)行介紹,Lock 是類(lèi)似于 synchronized 的另外一種鎖的使用,那么本節(jié)我們會(huì)對(duì) Lock 進(jìn)行詳細(xì)的介紹,主要知識(shí)點(diǎn)如下:
- Lock 接口的介紹,這是我們開(kāi)始認(rèn)識(shí) Lock 的敲門(mén)磚,本節(jié)課程的基礎(chǔ)知識(shí);
- Lock 接口相比于 synchronized 關(guān)鍵字的優(yōu)點(diǎn),這也是我們學(xué)習(xí) Lock 接口的意義所在;
- Lock 接口的常用方法介紹,了解 Lock 接口中的常用方法,是本節(jié)內(nèi)容的核心知識(shí)點(diǎn)。
Lock 是一個(gè)接口,并非一個(gè)實(shí)現(xiàn)類(lèi),本節(jié)內(nèi)容主要對(duì) Lock 接口進(jìn)行一個(gè)意義、結(jié)構(gòu)及方法的介紹,為后續(xù)講解 Lock 接口的實(shí)現(xiàn)類(lèi)常用鎖奠定一個(gè)扎實(shí)的基礎(chǔ)。
2. Lock 接口的介紹
Lock 接口的誕生:在 Java 中鎖的實(shí)現(xiàn)可以由 synchronized 關(guān)鍵字來(lái)完成,但在 Java5 之后,出現(xiàn)了一種新的方式來(lái)實(shí)現(xiàn),即 Lock 接口。
誕生的意義:Lock 接口支持那些語(yǔ)義不同(重入、公平等)的鎖規(guī)則,可以在非阻塞式結(jié)構(gòu)的上下文(包括 hand-over-hand 和鎖重排算法)中使用這些規(guī)則。主要的實(shí)現(xiàn)是 ReentrantLock。對(duì)于 ReentrantLock,后續(xù)有專(zhuān)門(mén)的小節(jié)進(jìn)行講解。
JDK 1.5 前的 synchronized:在多線程的情況下,當(dāng)一段代碼被 synchronized 修飾之后,同一時(shí)刻只能被一個(gè)線程訪問(wèn),其他線程都必須等到該線程釋放鎖之后才能有機(jī)會(huì)獲取鎖訪問(wèn)這段代碼。
Lock 接口: 實(shí)現(xiàn)提供了比使用 synchronized 方法和語(yǔ)句可獲得的更廣泛的鎖定操作。此實(shí)現(xiàn)允許更靈活的結(jié)構(gòu),可以具有差別很大的屬性,可以支持多個(gè)相關(guān)的 Condition 對(duì)象。
Lock 相對(duì)于 synchronized 關(guān)鍵字而言更加靈活,你可以自由得選擇你想要加鎖的地方。當(dāng)然更高的自由度也帶來(lái)更多的責(zé)任。
使用示例:我們通常會(huì)在 try catch 模塊中使用 Lock 關(guān)鍵字,在 finally 模塊中釋放鎖。
Lock lock = new ReentrantLock(); //通過(guò)子類(lèi)進(jìn)行創(chuàng)建,此處以ReentrantLock進(jìn)行舉例
lock.lock(); //加鎖
try {
// 對(duì)上鎖的邏輯進(jìn)行操作
} finally {
lock.unlock(); //釋放鎖
}
3. Lock 接口與 synchronized 關(guān)鍵字的區(qū)別
- 實(shí)現(xiàn):synchronized 關(guān)鍵字基于 JVM 層面實(shí)現(xiàn),JVM 控制鎖的獲取和釋放。Lock 接口基于 JDK 層面,手動(dòng)進(jìn)行鎖的獲取和釋放;
- 使用:synchronized 關(guān)鍵字不用手動(dòng)釋放鎖,Lock 接口需要手動(dòng)釋放鎖,在 finally 模塊中調(diào)用 unlock 方法;
- 鎖獲取超時(shí)機(jī)制:synchronized 關(guān)鍵字不支持,Lock 接口支持;
- 獲取鎖中斷機(jī)制:synchronized 關(guān)鍵字不支持,Lock 接口支持;
- 釋放鎖的條件:synchronized 關(guān)鍵字在滿足占有鎖的線程執(zhí)行完畢,或占有鎖的線程異常退出,或占有鎖的線程進(jìn)入 waiting 狀態(tài)才會(huì)釋放鎖。Lock 接口調(diào)用 unlock 方法釋放鎖;
- 公平性:synchronized 關(guān)鍵字為非公平鎖。Lock 接口可以通過(guò)入?yún)⒆孕性O(shè)置鎖的公平性。
4. Lock 接口相比 synchronized 關(guān)鍵字的優(yōu)勢(shì)
我們通過(guò)兩個(gè)個(gè)案例分析來(lái)了解 Lock 接口的優(yōu)勢(shì)所在。
案例 1 :在使用 synchronized 關(guān)鍵字的情形下,假如占有鎖的線程由于要等待 IO 或者其他原因(比如調(diào)用 sleep 方法)被阻塞了,但是又沒(méi)有釋放鎖,那么其他線程就只能一直等待,別無(wú)他法。這會(huì)極大影響程序執(zhí)行效率。
案例 1 分析:該案例體現(xiàn)了 synchronized 的缺陷,當(dāng)線程被占有時(shí),其他線程會(huì)陷入無(wú)條件的長(zhǎng)期等待。這是非常可怕的,因?yàn)橄到y(tǒng)資源有限,最終可能導(dǎo)致系統(tǒng)崩潰。
案例 1 解決:Lock 接口中的 tryLock (long time, TimeUnit unit) 方法或者響應(yīng)中斷 lockInterruptibly () 方法,能夠解決這種長(zhǎng)期等待的情況。
案例 2 :我們知道,當(dāng)多個(gè)線程讀寫(xiě)文件時(shí),讀操作和寫(xiě)操作會(huì)發(fā)生沖突現(xiàn)象,寫(xiě)操作和寫(xiě)操作也會(huì)發(fā)生沖突現(xiàn)象,但是讀操作和讀操作不會(huì)發(fā)生沖突現(xiàn)象。
但是如果采用 synchronized 關(guān)鍵字實(shí)現(xiàn)同步的話,就會(huì)導(dǎo)致一個(gè)問(wèn)題,即當(dāng)多個(gè)線程都只是進(jìn)行讀操作時(shí),也只有一個(gè)線程可以進(jìn)行讀操作,其他線程只能等待鎖的釋放而無(wú)法進(jìn)行讀操作。
案例 2 分析:該案例體現(xiàn)了 synchronized 的缺陷,悲觀鎖的缺陷。我們說(shuō)過(guò),如果只是讀操作,沒(méi)有增刪改操作的話,多線程環(huán)境下無(wú)需加鎖。但是這種情況下,如果在同一時(shí)間多個(gè)線程進(jìn)行讀操作,synchronized 會(huì) block 其他的讀操作,這是不合理的。
案例 2 解決:Lock 接口家族也可以解決這種情況,后續(xù)我們會(huì)對(duì) ReadWriteLock 接口的一個(gè)子類(lèi) ReentrantReadWriteLock 進(jìn)行講解。
總結(jié):Lock 接口實(shí)現(xiàn)提供了比使用 synchronized 方法和語(yǔ)句可獲得的更廣泛的鎖定操作,能夠解決 synchronized 不能夠避免的問(wèn)題。
5. Lock 接口的常用方法
我們來(lái)簡(jiǎn)單的看下,JDK 中 Lock 接口的源碼中所包含的方法:
public interface Lock {
void lock();
void lockInterruptibly() throws InterruptedException;
boolean tryLock();
boolean tryLock(long time, TimeUnit unit) throws InterruptedException;
void unlock();
Condition newCondition();
}
方法介紹:
- void lock():獲取鎖。如果鎖不可用,出于線程調(diào)度目的,將禁用當(dāng)前線程,并且在獲得鎖之前,該線程將一直處于休眠狀態(tài);
- void lockInterruptibly():如果當(dāng)前線程未被中斷,則獲取鎖;
- boolean tryLock():僅在調(diào)用時(shí)鎖為空閑狀態(tài)才獲取該鎖。如果鎖可用,則獲取鎖,并立即返回值 true。如果鎖不可用,則此方法將立即返回值 false;
- boolean tryLock(long time, TimeUnit unit):如果鎖在給定的等待時(shí)間內(nèi)空閑,并且當(dāng)前線程未被中斷,則獲取鎖;
- void unlock():釋放鎖。在等待條件前,鎖必須由當(dāng)前線程保持。調(diào)用 Condition.await () 將在等待前以原子方式釋放鎖,并在等待返回前重新獲取鎖;
- Condition newCondition():返回綁定到此 Lock 實(shí)例的新 Condition 實(shí)例。
Tips:對(duì) Lock 接口方法的使用,我們必須基于子類(lèi)進(jìn)行 Lock 的創(chuàng)建來(lái)展示,由于目前我們還未接觸 Lock 接口的實(shí)現(xiàn)子類(lèi),此處只做方法的介紹。后續(xù)對(duì) ReentrantLock 進(jìn)行講解時(shí),會(huì)進(jìn)行深入講解。
6. 小結(jié)
本節(jié)主要是對(duì) Lock 接口的常用方法進(jìn)行了介紹,為本節(jié)內(nèi)容的核心知識(shí)。除了方法的介紹外,本節(jié)內(nèi)容不容忽視的一個(gè)重點(diǎn)內(nèi)容是 synchronized 關(guān)鍵字與 Lock 接口的區(qū)別,以及 Lock 接口的優(yōu)勢(shì)所在。
掌握本節(jié)內(nèi)容,有助于同學(xué)對(duì)后續(xù)實(shí)現(xiàn)類(lèi)鎖的學(xué)習(xí),為后續(xù)的學(xué)習(xí)奠定了良好的基礎(chǔ)。