前言
最近在使用BlockQueue和ConcurrentHashMap中都有個(gè)核心的東西ReentrantLock,網(wǎng)上有很多關(guān)于重入鎖的介紹。下面自己的幾個(gè)問(wèn)題陋气,然后再通過(guò)自己的實(shí)現(xiàn)來(lái)把問(wèn)題講清楚。
API官方解釋
一個(gè)可重入的互斥鎖 Lock,它具有與使用 synchronized 方法和語(yǔ)句所訪問(wèn)的隱式監(jiān)視器鎖相同的一些基本行為和語(yǔ)義匠童,但功能更強(qiáng)大。 ReentrantLock 將由最近成功獲得鎖塑顺,并且還沒有釋放該鎖的線程所"擁有"汤求。當(dāng)鎖沒有被另一個(gè)線程所擁有時(shí),調(diào)用 lock 的線程將成功獲取該鎖并返回严拒。如果當(dāng)前線程已經(jīng)擁有該鎖扬绪,此方法將立即返回】氵耄可以使用 isHeldByCurrentThread() 和 getHoldCount() 方法來(lái)檢查此情況是否發(fā)生挤牛。
此類的構(gòu)造方法接受一個(gè)可選的公平 參數(shù)。當(dāng)設(shè)置為 true 時(shí)种蘸,在多個(gè)線程的爭(zhēng)用下墓赴,這些鎖傾向于將訪問(wèn)權(quán)授予等待時(shí)間最長(zhǎng)的線程竞膳。否則此鎖將無(wú)法保證任何特定訪問(wèn)順序。與采用默認(rèn)設(shè)置(使用不公平鎖)相比竣蹦,使用公平鎖的程序在許多線程訪問(wèn)時(shí)表現(xiàn)為很低的總體吞吐量(即速度很慢顶猜,常常極其慢),但是在獲得鎖和保證鎖分配的均衡性時(shí)差異較小痘括。不過(guò)要注意的是长窄,公平鎖不能保證線程調(diào)度的公平性。因此纲菌,使用公平鎖的眾多線程中的一員可能獲得多倍的成功機(jī)會(huì)挠日,這種情況發(fā)生在其他活動(dòng)線程沒有被處理并且目前并未持有鎖時(shí)。還要注意的是翰舌,未定時(shí)的 tryLock 方法并沒有使用公平設(shè)置嚣潜。因?yàn)榧词蛊渌€程正在等待,只要該鎖是可用的椅贱,此方法就可以獲得成功懂算。
典型用法
class X {
private final ReentrantLock lock = new ReentrantLock();
// ...
public void m() {
lock.lock(); // block until condition holds
try {
// ... method body
} finally {
lock.unlock()
}
}
}
為什么理解ReentrantLock
- JDK里面的ReentrantLock實(shí)現(xiàn)非常精巧,ConcurrentHashMap庇麦,各種Queue都是基于ReentrantLock實(shí)現(xiàn)的计技,所以,了解ReentrantLock是十分必要的山橄。
- ReentrantLock使用非常靈活垮媒,synchronized是JVM實(shí)現(xiàn)的,用起來(lái)不那么靈活航棱。
- 了解內(nèi)部實(shí)現(xiàn)原理睡雇,可以更好的幫助我們?cè)陂_發(fā)中選型,也能更加容易理解并發(fā)包下其他實(shí)現(xiàn)類的原理饮醇。
ReentrantLock和synchronized
java中存在兩種鎖它抱,一種是JDK實(shí)現(xiàn)的synchronized,還有一種是Lock朴艰,兩者用法相似观蓄,又各有特點(diǎn),了解其中的區(qū)別呵晚,才能更好的取舍。
實(shí)現(xiàn)
- synchronized是基于JVM實(shí)現(xiàn)的沫屡,Lock是基于java API實(shí)現(xiàn)的饵隙,我們可以通過(guò)源碼就能知道Lock的實(shí)現(xiàn)。
- 兩者的實(shí)現(xiàn)思路一致沮脖,都是抽象出來(lái)一個(gè)同步隊(duì)列和一個(gè)等待隊(duì)列金矛,爭(zhēng)搶鎖的線程不斷在同步隊(duì)列和等待隊(duì)列中不斷轉(zhuǎn)換芯急。
- synchronized基于并發(fā)程度抽象出偏向鎖、輕量級(jí)鎖和重量級(jí)鎖驶俊,而Lock沒有這樣的概念娶耍。
使用
- 對(duì)于使用者的直觀體驗(yàn)上Lock是比較復(fù)雜的,需要lock和unlock饼酿,如果忘記釋放鎖就會(huì)產(chǎn)生死鎖的問(wèn)題榕酒,通常需要在finally中進(jìn)行鎖的釋放。但是synchronized的使用十分簡(jiǎn)單故俐,只需要對(duì)自己的方法或者關(guān)注的同步對(duì)象或類使用synchronized關(guān)鍵字即可
特點(diǎn)
功能 | synchronized | ReentrantLock |
---|---|---|
鎖獲取超時(shí) | 不支持 | 支持 |
獲取鎖響應(yīng)中斷 | 不支持 | 支持 |
是否要手動(dòng)釋放鎖 | 否 | 是 |
性能
- 早期的synchronized是十分低下的想鹰,在1.5之后引入了偏向鎖,輕量級(jí)鎖药版,重量級(jí)鎖辑舷,同時(shí)也優(yōu)化了鎖的爭(zhēng)搶流程,大大提高 了synchronized的性能槽片,同時(shí)在后面的版本中也還在對(duì)synchronized有優(yōu)化何缓。后面在JDK1.8中,ConcurrentHashMap取消了ReentrantLock的設(shè)計(jì)还栓,而是直接用synchronized碌廓,因?yàn)閟ynchronized在數(shù)據(jù)量少時(shí)性能足夠優(yōu)秀,而且整個(gè)流程更加簡(jiǎn)單蝙云,具體可以參考談?wù)凜oncurrentHashMap1.7和1.8的不同實(shí)現(xiàn)
- ReentrantLock沒有像synchronized那樣對(duì)鎖劃分成多個(gè)等級(jí)氓皱,根據(jù)并發(fā)程度的不同采取不同的策略,由于synchronized在競(jìng)爭(zhēng)激烈的情況下會(huì)做鎖的升級(jí)勃刨,性能急劇下降波材。所以ReentrantLock在競(jìng)爭(zhēng)比較激烈的時(shí)候性能是很穩(wěn)定的,少量競(jìng)爭(zhēng)或者單線程的時(shí)候可能會(huì)遜色一些身隐。
源碼分析
- ReentrantLock實(shí)現(xiàn)Lock接口嘹害。Lock 實(shí)現(xiàn)提供了比使用 synchronized 方法和語(yǔ)句可獲得的更廣泛的鎖定操作,此實(shí)現(xiàn)允許更靈活的結(jié)構(gòu)喷户,可以具有差別很大的屬性始绍,可以支持多個(gè)相關(guān)的 Condition
對(duì)象。 - ReentrantLock內(nèi)部是由Sync垢揩,NonfairSync(非公平同步)玖绿,F(xiàn)airSync(公平同步)三個(gè)內(nèi)部類實(shí)現(xiàn),且這三個(gè)內(nèi)部類都實(shí)現(xiàn)繼承自非常著名的AbstractQueuedSynchronizer叁巨,簡(jiǎn)寫AQS斑匪,中文名叫隊(duì)列同步器,了解這個(gè)隊(duì)列同步器锋勺,基本上就完成對(duì)整個(gè)concurrent包下的鎖完全了解了蚀瘸。很多人不了解為何設(shè)計(jì)成內(nèi)部類狡蝶,我記得有一句話講得好,內(nèi)部類就像人體的一個(gè)器官贮勃,它自己有完整的功能贪惹,也能給身體提供功能,如果不把它設(shè)計(jì)成一個(gè)內(nèi)部類就會(huì)很凌亂寂嘉。
- 默認(rèn)構(gòu)造成NonfairSync奏瞬。源碼如下
/**
* Creates an instance of {@code ReentrantLock}.
* This is equivalent to using {@code ReentrantLock(false)}.
*/
public ReentrantLock() {
sync = new NonfairSync();
}
- 調(diào)用Lock方法,我們先從默認(rèn)非公平鎖開始分析
/**
* Performs lock. Try immediate barge, backing up to normal
* acquire on failure.
*/
final void lock() {
if (compareAndSetState(0, 1))
// 設(shè)置當(dāng)前擁有獨(dú)占訪問(wèn)的線程垫释。
setExclusiveOwnerThread(Thread.currentThread());
else
acquire(1);
}
代碼一開始就直接用compareAndSetState丝格,這是典型的CAS操作,關(guān)于CAS操作可以參考java基礎(chǔ):CAS操作棵譬,然后就是設(shè)置當(dāng)前擁有獨(dú)占訪問(wèn)的線程显蝌,否則執(zhí)行acquire(1)
非公平鎖的acquire(1)實(shí)現(xiàn)
public final void acquire(int arg) {
if (!tryAcquire(arg) &&
acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
selfInterrupt();
}
tryAcquire的底層實(shí)現(xiàn)
final boolean nonfairTryAcquire(int acquires) {
final Thread current = Thread.currentThread();
int c = getState();
if (c == 0) {
if (compareAndSetState(0, acquires)) {
setExclusiveOwnerThread(current);
return true;
}
}
else if (current == getExclusiveOwnerThread()) {
int nextc = c + acquires;
if (nextc < 0) // overflow
throw new Error("Maximum lock count exceeded");
setState(nextc);
return true;
}
return false;
}
意思就是如果沒有,就設(shè)置當(dāng)前擁有獨(dú)占訪問(wèn)的線程订咸,設(shè)置完后就new一個(gè)Node節(jié)點(diǎn)添加到AQS隊(duì)列中去等曼尊,等獲得到空閑時(shí)就會(huì)設(shè)置當(dāng)前擁有獨(dú)自訪問(wèn)的線程,完事后就調(diào)用Thread.currentThread().interrupt();中斷線程一直去嘗試獲取脏嚷。
- 公平鎖Lock實(shí)現(xiàn)
final void lock() {
acquire(1);
}
我們發(fā)現(xiàn)公平鎖直接就調(diào)用acquire(1)方法骆撇,與非公平鎖不同的是缺少CAS操作。
- lock.unLock();實(shí)現(xiàn)
protected final boolean tryRelease(int releases) {
int c = getState() - releases;
if (Thread.currentThread() != getExclusiveOwnerThread())
throw new IllegalMonitorStateException();
boolean free = false;
if (c == 0) {
free = true;
setExclusiveOwnerThread(null);
}
setState(c);
return free;
}
不管是公平鎖還是非公平鎖父叙,unLock實(shí)現(xiàn)都一樣神郊,基本上就是設(shè)置一些狀態(tài),然后把獨(dú)占線程清空趾唱。
什么時(shí)候選擇用 ReentrantLock 代替 synchronized
在早期的版本中涌乳,synchronized性能其實(shí)非常底的,但在1.8之后甜癞,對(duì)synchronized的優(yōu)化已經(jīng)足夠好了夕晓,甚至一些情況下,synchronized性能比ReentrantLock還要好悠咱,所以在1.8版本的ConcurrentHashMap實(shí)現(xiàn)取消了ReetrantLock設(shè)計(jì)蒸辆,直接用synchronized。這并不代表ReetrantLock就不行了析既,在高并發(fā)及多線程環(huán)境下躬贡,ReetrantLock的性能依舊是最優(yōu)選擇。
總結(jié)
了解ReentrantLock其實(shí)最主要的兩點(diǎn)眼坏,一是CAS操作拂玻,二是AQS,當(dāng)然AQS是一切并發(fā)包下基礎(chǔ),我們只有了解其中的原理纺讲,就很容易了解這些鎖的調(diào)用關(guān)系,其實(shí)都是AQS的一些應(yīng)用囤屹,比如公平鎖與非公平鎖的區(qū)別就是是否獲得鎖時(shí)是否有CAS操作熬甚,有的是非公平鎖,沒有的是公平鎖肋坚。因?yàn)镽eentrantLock是對(duì)像乡括,synchronized的一些用法都可以用ReentrantLock實(shí)現(xiàn),而一些時(shí)間鎖等候智厌、可中斷鎖等候诲泌、無(wú)塊結(jié)構(gòu)鎖、多個(gè)條件變量或者鎖投票是synchronized不具備的铣鹏,而ReentrantLock的性能在多線程環(huán)境中優(yōu)勢(shì)相當(dāng)明顯敷扫,所以理解ReentrantLock是對(duì)多線程和大并發(fā)環(huán)境的編程多了一種技術(shù)選型。