Lock
Lock接口定義了一組抽象的加鎖操作:
public interface Lock {
//獲取鎖,會(huì)一直等待
void lock();
//獲取鎖,否則一直等待,但是等待狀態(tài)可以被其他線程中斷
void lockInterruptibly() throws InterruptedException;
//嘗試獲得鎖,沒(méi)搶到直接返回失敗
boolean tryLock();
//嘗試獲取鎖,沒(méi)搶到則會(huì)等待一段時(shí)間,等待狀態(tài)可以被其他線程中斷
boolean tryLock(long time, TimeUnit unit) throws InterruptedException;
//釋放鎖
void unlock();
//生成鎖的條件變量,線程獲取鎖后,可以等待或者通知該條件變量
Condition newCondition();
}
與內(nèi)置的 Monitor 鎖(也叫內(nèi)部鎖,內(nèi)置鎖)不同,Lock 提供了多種獲取鎖的方式(無(wú)條件的,可輪詢的,定時(shí)的以及可中斷的),所有的加鎖和解鎖的方法都是顯示的。
Lock 的實(shí)現(xiàn)類必須提供與內(nèi)部鎖相同的內(nèi)存可見(jiàn)性,但在加鎖語(yǔ)義,調(diào)度算法,順序保證以及性能特性有所不同。
可重入鎖 ReentrantLock
ReentrantLock 實(shí)現(xiàn)了 Lock 接口,并提供了與 synchronized 相同的互斥和內(nèi)存可見(jiàn)性,ReentrantLock 并不是替代內(nèi)置鎖的方法,而是當(dāng)內(nèi)置鎖機(jī)制不適用時(shí),作為一種可以選擇的補(bǔ)充的高級(jí)功能。
對(duì)比 synchronized
ReentrantLock 是 synchronized 的功能補(bǔ)充,它們之間的差異可以用下表表示:
| synchronized | ReentrantLock
------------- | ------------- | -------------
獲取鎖方式 | 搶鎖失敗只能無(wú)限等待 | 提供多種等待鎖方式,可以無(wú)限等待
| | 可以限制等待超時(shí)
| | 等待時(shí)可以被中斷
| | 可以無(wú)阻塞嘗試獲取鎖
等待線程調(diào)度 | 未知,視 JVM 實(shí)現(xiàn) | 公平鎖:FIFO,按照進(jìn)入同步隊(duì)列順序
| | 非公平鎖:第一次獲取鎖時(shí)有機(jī)會(huì)插隊(duì)
編程便利性 | 獲取鎖/等待鎖/釋放鎖都有內(nèi)部實(shí)現(xiàn),使用便利 | 需要顯示獲取鎖,釋放鎖,切記要捕捉 exception, 在 finally 中釋放鎖
性能 | 略低,Java6 后有顯著提高 | 較高,Java6 后差距減小
調(diào)試 | 線程轉(zhuǎn)儲(chǔ)中給出哪些調(diào)用幀獲得哪些鎖 | Java6 后提供管理和調(diào)試接口,鎖需要通過(guò)該接口注冊(cè),相關(guān)加鎖信息出現(xiàn)在線程轉(zhuǎn)儲(chǔ)中
未來(lái)更可能提升 synchronized 性能,因?yàn)?synchronized 是 JVM 內(nèi)置屬性,它能執(zhí)行一些優(yōu)化,比如增加鎖的粒度來(lái)消除內(nèi)置鎖的同步,對(duì)于類庫(kù)里的 ReentrantLock 則優(yōu)化余地不大。所以綜合未來(lái)性能,調(diào)試,特別是編程便利性來(lái)說(shuō),synchronized 是使用時(shí)的第一選擇,只有當(dāng)它的功能不滿足,或者程序運(yùn)行在 Java5 或之前,才考慮 ReentrantLock。
公平性
ReentrantLock 的構(gòu)造函數(shù)中提供兩個(gè)公平性選擇:創(chuàng)建一個(gè)非公平(默認(rèn))的鎖,或者公平鎖。
公平鎖
線程按照他們發(fā)出請(qǐng)求的順序來(lái)獲得鎖。如果有另一個(gè)線程持有這個(gè)鎖或者有其他線程在隊(duì)列中等待這個(gè)鎖,那么新發(fā)出請(qǐng)求的線程將放入隊(duì)列中。
但是即便對(duì)于公平鎖而已,可輪詢的 tryLock 仍然會(huì)有可能插隊(duì)。
非公平鎖
非公平鎖允許“插隊(duì)”:當(dāng)一個(gè)線程請(qǐng)求非公平的鎖時(shí),如果在發(fā)出請(qǐng)求的同時(shí)該鎖的狀態(tài)變?yōu)榭捎茫敲催@個(gè)線程將跳過(guò)排隊(duì)隊(duì)列,直接獲取這個(gè)鎖。
只有當(dāng)鎖被某個(gè)線程持有時(shí),新發(fā)出請(qǐng)求的線程才會(huì)被放入隊(duì)列中。
為什么要將非公平鎖作為默認(rèn)鎖,因?yàn)樵趯?shí)際應(yīng)用中,確保被阻塞的線程能夠最終獲得鎖就可以了,絕對(duì)的公平產(chǎn)生的開(kāi)銷也較大,絕大多數(shù)情況下,非公平鎖的性能要高于公平鎖。
內(nèi)部實(shí)現(xiàn)
可以把 ReentrantLock 想象成代理模式,其中最核心的加鎖/解鎖等操作其實(shí)都是由 Sync 對(duì)象完成的,Sync 的 acquire 系列接口實(shí)現(xiàn)加鎖, release 系列接口實(shí)現(xiàn)解鎖。對(duì)于公平鎖和非公平鎖,分別由 FairSync 和 NonfairSync 實(shí)現(xiàn)。它們之間的類圖關(guān)系,如下圖所示:

其中最核心的部分是 AbstractQueuedSynchronizer,簡(jiǎn)稱 AQS。這個(gè)類也是其他同步類的基類,AQS 是一個(gè)用戶構(gòu)建鎖和同步器的框架,許多同步器都可以通過(guò) AQS 快速的構(gòu)造出來(lái)。不僅是 ReentrantLock, 也包括 Semaphore, CountDownLatch, ReentrantReadWriteLock, SynchronouszQueue 和 FurtureTask。
因?yàn)槠?,最核心?AQS 不妨放到下篇來(lái)記錄。
最佳編程模式
class X {
private final ReentrantLock lock = new ReentrantLock();
// ...
public void m() {
lock.lock(); // block until condition holds
try {
// ... method body
} finally {
lock.unlock();
}
}
}
切記 finally 中釋放鎖。
內(nèi)容來(lái)源
Java 并發(fā)編程實(shí)戰(zhàn)