Java并發(fā)編程之鎖機制之(ReentrantLock)重入鎖

ReentrantLock基本介紹

ReentrantLock是一種可重入的互斥鎖孽查,它具有與使用synchronized方法和語句所訪問的隱式監(jiān)視器鎖相同的一些基本行為和語義胳螟,但功能更強大棍郎。

ReentrantLock將由最近成功獲得鎖,并且還沒有釋放該鎖的線程所擁有逆甜。當鎖沒有被另一個線程所擁有時,調用 lock 的線程將成功獲取該鎖并返回。如果當前線程已經擁有該鎖虫给,此方法將立即返回∠辣蹋可以使用isHeldByCurrentThread()和getHoldCount()方法來檢查此情況是否發(fā)生抹估。

此類的構造方法接受一個可選的公平參數(shù)。當設置為 true 時(也是當前ReentrantLock為公平鎖的情況)弄兜,在多個線程的爭用下药蜻,這些鎖傾向于將訪問權授予等待時間最長的線程。否則此鎖將無法保證任何特定訪問順序替饿。與采用默認設置(使用不公平鎖)相比语泽,使用公平鎖的程序在許多線程訪問時表現(xiàn)為很低的總體吞吐量(即速度很慢,常常極其慢)视卢,但是在獲得鎖和保證鎖分配的均衡性時差異較小踱卵。不過要注意的是,公平鎖不能保證線程調度的公平性据过。因此惋砂,使用公平鎖的眾多線程中的一員可能獲得多倍的成功機會,這種情況發(fā)生在其他活動線程沒有被處理并且目前并未持有鎖時绳锅。還要注意的是西饵,未定時的 tryLock 方法并沒有使用公平設置。因為即使其他線程正在等待榨呆,只要該鎖是可用的罗标,此方法就可以獲得成功庸队。

ReentrantLock 類基本結構

通過上文的簡單介紹后,我相信很多小伙伴還是一臉懵逼闯割,只知道上文我們提到了ReentrantLock與synchronized相比有相同的語義彻消,同時其內部分為了公平鎖與非公平鎖兩種鎖的類型,且該鎖是支持重進入的宙拉。那么為了方便大家理解這些知識點宾尚,我們先從其類的基本結構講起。具體類結構如下圖所示:

從上圖中我們可以看出谢澈,在ReentrantLock類中煌贴,定義了三個靜態(tài)內部類,Sync锥忿、FairSync(公平鎖)牛郑、NonfairSync(非公平鎖)。其中Sync繼承了AQS(AbstractQueuedSynchronizer)敬鬓,而FairSync與NonfairSync又分別繼承了Sync淹朋。關于ReentrantLock基本類結構如下所示:

public class ReentrantLock implements Lock, java.io.Serializable {

? ? private final Sync sync;

//默認無參構造函數(shù),默認為非公平鎖

? ? public ReentrantLock() {

? ? ? ? sync = new NonfairSync();

? ? }

//帶參數(shù)的構造函數(shù)钉答,用戶自己來決定是公平鎖還是非公平鎖

? ? public ReentrantLock(boolean fair) {

? ? ? ? sync = fair ? new FairSync() : new NonfairSync();

? ? }

? ? //抽象基類繼承AQS础芍,公平鎖與非公平鎖繼承該類,并分別實現(xiàn)其lock()方法

? ? abstract static class Sync extends AbstractQueuedSynchronizer {

? ? ? ? abstract void lock();

? ? ? ? //省略部分代碼..

? ? }


//非公平鎖實現(xiàn)

? ? static final class NonfairSync extends Sync {...}


? ? //公平鎖實現(xiàn)

? ? static final class FairSync extends Sync {....}


? ? //鎖實習数尿,根據具體子類實現(xiàn)調用

? ? public void lock() {

? ? ? ? sync.lock();

? ? }

//響應中斷的獲取鎖

? ? public void lockInterruptibly() throws InterruptedException {

? ? ? ? sync.acquireInterruptibly(1);

? ? }

//嘗試獲取鎖仑性,默認采用非公平鎖方法實現(xiàn)

? ? public boolean tryLock() {

? ? ? ? return sync.nonfairTryAcquire(1);

? ? }

//超時獲取鎖

? ? public boolean tryLock(long timeout, TimeUnit unit)

? ? ? ? ? ? throws InterruptedException {

? ? ? ? return sync.tryAcquireNanos(1, unit.toNanos(timeout));

? ? }

//釋放鎖

? ? public void unlock() {

? ? ? ? sync.release(1);

? ? }

? ? //創(chuàng)建鎖條件(從Condetion來理解,就是創(chuàng)建等待隊列)

? ? public Condition newCondition() {

? ? ? ? return sync.newCondition();

? ? }

? ? //省略部分代碼....

}

復制代碼

這里為了方便大家理解ReentrantLock類的整體結構右蹦,我省略了一些代碼及重新排列了一些代碼的順序诊杆。

從代碼中我們可以看出。整個ReentrantLock類的實現(xiàn)其實都是交給了其內部FairSync與NonfairSync兩個類何陆。在ReentrantLock類中有兩個構造函數(shù)刽辙,其中不帶參數(shù)的構造函數(shù)中默認使用的NonfairSync(非公平鎖)。另一個帶參數(shù)的構造函數(shù)甲献,用戶自己來決定是FairSync(公平鎖)還是非公平鎖。

重進入實現(xiàn)

在上文中颂翼,我們提到了ReentrantLock是支持重進入的晃洒,那什么是重進入呢?重進入是指任意線程在獲取到鎖之后能夠再次獲取該鎖朦乏,而不會被鎖阻塞球及。那接下來我們看看這個例子,如下所示:

class ReentrantLockDemo {

? ? private static final ReentrantLock lock = new ReentrantLock();


? ? public static void main(String[] args) {

? ? ? ? Thread thread = new Thread(new Runnable() {

? ? ? ? ? ? @Override

? ? ? ? ? ? public void run() {

? ? ? ? ? ? ? ? methodA();

? ? ? ? ? ? }

? ? ? ? });

? ? ? ? thread.start();

? ? }


? ? public static void methodA() {

? ? ? ? lock.lock();

? ? ? ? try {

? ? ? ? ? ? System.out.println("我已經進入methodA方法了");

? ? ? ? ? ? methodB();//方法A中繼續(xù)調用方法B

? ? ? ? } finally {

? ? ? ? ? ? lock.unlock();

? ? ? ? }

? ? }

? ? public static void methodB() {

? ? ? ? lock.lock();

? ? ? ? try {

? ? ? ? ? ? System.out.println("我已經進入methodB方法了");

? ? ? ? } finally {

? ? ? ? ? ? lock.unlock();

? ? ? ? }

? ? }

}

//輸出結果

我已經進入methodA方法了

我已經進入methodB方法了

復制代碼

在上述代碼中我們聲明了一個線程調用methodA()方法呻疹。同時在該方法內部我們又調用了methodB()方法吃引。從實際的代碼運行結果來看,當前線程進入方法A之后。在方法B中再次調用lock.lock();時镊尺,該線程并沒有被阻塞朦佩。也就是說ReentrantLock是支持重進入的。那下面我們就一起來看看其內部的實現(xiàn)原理庐氮。

因為ReenTrantLock將具體實現(xiàn)交給了NonfairSync(非公平鎖)與FairSync(公平鎖)语稠。同時又因為上述提到的兩個鎖,關于重進入的實現(xiàn)又非常相似弄砍。所以這里將采用NonfairSync(非公平鎖)的重進入的實現(xiàn)仙畦,來進行分析。希望讀者朋友們閱讀到這里的時候需要注意音婶,不是我懶哦慨畸,是真的很相似哦。

好了下面我們來看代碼衣式。關于NonfairSync代碼如下所示:

static final class NonfairSync extends Sync {

? ? ? ? final void lock() {

? ? ? ? ? ? if (compareAndSetState(0, 1))////直接獲取同步狀態(tài)成功寸士,那么就不再走嘗試獲取鎖的過程

? ? ? ? ? ? ? ? setExclusiveOwnerThread(Thread.currentThread());

? ? ? ? ? ? else

? ? ? ? ? ? ? ? acquire(1);

? ? ? ? }

? ? ? ? protected final boolean tryAcquire(int acquires) {

? ? ? ? ? ? return nonfairTryAcquire(acquires);

? ? ? ? }

? ? }

復制代碼

當我們調用lock()方法時,通過CAS操作將AQS中的state的狀態(tài)設置為1瞳收,如果成功碉京,那么表示獲取同步狀態(tài)成功。那么會接著調用setExclusiveOwnerThread(Thread thread)方法來設置當前占有鎖的線程螟深。如果失敗谐宙,則調用acquire(int arg)方法來獲取同步狀態(tài)(該方法是屬于AQS中的獨占式獲取同步狀態(tài)的方法,對該方法不熟悉的小伙伴界弧,建議閱讀Java并發(fā)編程之鎖機制之AQS(AbstractQueuedSynchronizer))凡蜻。而該方法內部會調用tryAcquire(int acquires)來嘗試獲取同步狀態(tài)。通過觀察垢箕,我們發(fā)現(xiàn)最終會調用Sync類中的nonfairTryAcquire(int acquires)方法划栓。我們繼續(xù)跟蹤。

final boolean nonfairTryAcquire(int acquires) {

? ? //獲取當前線程

? ? ? ? ? ? final Thread current = Thread.currentThread();

? ? ? ? ? ? int c = getState();

? ? ? ? ? ? //(1)判斷同步狀態(tài)条获,如果未設置忠荞,則設置同步狀態(tài)

? ? ? ? ? ? if (c == 0) {

? ? ? ? ? ? ? ? if (compareAndSetState(0, acquires)) {

? ? ? ? ? ? ? ? ? ? setExclusiveOwnerThread(current);

? ? ? ? ? ? ? ? ? ? return true;

? ? ? ? ? ? ? ? }

? ? ? ? ? ? }

? ? ? ? ? ? //(2)如果當前線程已經獲取了同步狀態(tài),則增加同步狀態(tài)的值帅掘。

? ? ? ? ? ? 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;

? ? ? ? }

復制代碼

從代碼上來看委煤,該方法主要走兩個步驟,具體如下所示:

(1)先判斷同步狀態(tài)修档, 如果未曾設置碧绞,則設置同步狀態(tài),并設置當前占有鎖的線程吱窝。

(2)判斷是否是同一線程讥邻,如果當前線程已經獲取了同步狀態(tài)(也就是獲取了鎖)迫靖,那么增加同步狀態(tài)的值。

也就是說兴使,如果同一個鎖獲取了鎖N(N為正整數(shù))次系宜,那么對應的同步狀態(tài)(state)也就等于N。那么接下來的問題來了鲫惶,如果當前線程重復N次獲取了鎖蜈首,那么該線程是否需要釋放鎖N次呢?答案當然是必須的欠母。當我們調用ReenTrantLock的unlock()方法來釋放同步狀態(tài)(也就是釋放鎖)時欢策,內部會調用sync.release(1);。最終會調用Sync類的tryRelease(int releases)方法赏淌。具體代碼如下所示:

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()方法會將當前同步狀態(tài)減一六水。也就是說如果當前線程獲取了鎖N次俺孙,那么獲取鎖的相應線程也需要調用unlock()方法N次。這也是為什么我們在之前的重入鎖例子中掷贾,為什么methodB方法中也要釋放鎖的原因睛榄。

非公平鎖

在ReentrantLock中有著非公平鎖與公平鎖的概念,這里我先簡單的介紹一下公平這兩個字的含義想帅。?這里的公平是指線程獲取鎖的順序场靴。也就是說鎖的獲取順序是按照當前線程請求的絕對時間順序,當然前提條件下是該線程獲取鎖成功?港准。

那么接下來旨剥,我們來分析在ReentrantLock中的非公平鎖的具體實現(xiàn)。

這里需要大家具備AQS(AbstractQueuedSynchronizer)類的相關知識浅缸。如果大家不熟悉這塊的知識轨帜。建議大家閱讀Java并發(fā)編程之鎖機制之AQS(AbstractQueuedSynchronizer)

static final class NonfairSync extends Sync {

? ? ? ? private static final long serialVersionUID = 7316153563782823691L;

? ? ? ? final void lock() {

? ? ? ? ? ? if (compareAndSetState(0, 1))//直接獲取同步狀態(tài)成功衩椒,那么就不再走嘗試獲取鎖的過程

? ? ? ? ? ? ? ? setExclusiveOwnerThread(Thread.currentThread());

? ? ? ? ? ? else

? ? ? ? ? ? ? ? acquire(1);

? ? ? ? }

? ? ? ? //省略部分代碼...

? ? }

復制代碼

當在ReentrantLock在非公平鎖的模式下蚌父,去調用lock()方法。那么接下來最終會走AQS(AbstractQueuedSynchronizer)下的acquire(int arg)(獨占式的獲取同步狀態(tài))毛萌,也就是如下代碼:

public final void acquire(int arg) {

? ? ? ? if (!tryAcquire(arg) &&

? ? ? ? ? ? acquireQueued(addWaiter(Node.EXCLUSIVE), arg))

? ? ? ? ? ? selfInterrupt();

? ? }

復制代碼

那么結合之前我們所講的AQS知識梢什,在多個線程在獨占式請求共享狀態(tài)下(也就是請求鎖)的情況下,在AQS中的同步隊列中的線程節(jié)點情況如下圖所示:

那么我們試想一種情況朝聋,當Nod1中的線程執(zhí)行完相應任務后,釋放鎖后囤躁。這個時候本來該喚醒當前線程節(jié)點的下一個節(jié)點,也就是Node2中的線程冀痕。這個時候突然另一線程突然來獲取線程(這里我們用節(jié)點Node5來表示)荔睹。具體情況如下圖所示:

那么根據AQS中獨占式獲取同步狀態(tài)的邏輯。只要Node5對應的線程獲取同步狀態(tài)成功言蛇。那么就會出現(xiàn)下面的這種情況僻他,具體情況如下圖所示:在此我向大家推薦一個架構學習交流裙。交流學習裙號:821169538腊尚,里面會分享一些資深架構師錄制的視頻錄像

從上圖中我們可以看出吨拗,由于Node5對象的線程搶占了獲取同步狀態(tài)(獲取鎖)的機會,本身應該被喚醒的Node2線程節(jié)點婿斥。因為獲取同步狀態(tài)失敗劝篷。所以只有再次的陷入阻塞。那么綜上民宿。我們可以知道娇妓。非公平鎖獲取同步狀態(tài)(獲取鎖)時不會考慮同步隊列中中等待的問題。會直接嘗試獲取鎖活鹰。也就是會存在后申請哈恰,但是會先獲得同步狀態(tài)(獲取鎖)的情況。

公平鎖

理解了非公平鎖志群,再來理解公平鎖就非常簡單了着绷。下面我們來看一下公平鎖與非公平鎖的加鎖的源碼:

從源碼我們可以看出,非公平鎖與公平鎖之間的代碼唯一區(qū)別就是多了一個判斷條件!hasQueuedPredecessors()(圖中紅框所示)锌云。那我們查看其源碼(該代碼在AQS中荠医,強烈建議閱讀Java并發(fā)編程之鎖機制之AQS(AbstractQueuedSynchronizer)

public final boolean hasQueuedPredecessors() {

? ? ? ? Node t = tail;

? ? ? ? Node h = head;

? ? ? ? Node s;

? ? ? ? return h != t &&

? ? ? ? ? ? ((s = h.next) == null || s.thread != Thread.currentThread());

? ? }

復制代碼

代碼理解理解起來非常簡單,就是判斷當前當前head節(jié)點的next節(jié)點是不是當前請求同步狀態(tài)(請求鎖)的線程宾抓。也就是語句((s = h.next) == null || s.thread != Thread.currentThread()子漩。那么接下來結合AQS中的同步隊列我們可以得到下圖:

那么綜上我們可以得出,公平鎖保證了線程請求的同步狀態(tài)(請求鎖)的順序。不會出現(xiàn)另一個線程搶占的情況抢野。

?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
  • 序言:七十年代末渣淳,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子缕棵,更是在濱河造成了極大的恐慌,老刑警劉巖涉兽,帶你破解...
    沈念sama閱讀 218,755評論 6 507
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件招驴,死亡現(xiàn)場離奇詭異,居然都是意外死亡枷畏,警方通過查閱死者的電腦和手機别厘,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,305評論 3 395
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來拥诡,“玉大人触趴,你說我怎么就攤上這事氮发。” “怎么了冗懦?”我有些...
    開封第一講書人閱讀 165,138評論 0 355
  • 文/不壞的土叔 我叫張陵爽冕,是天一觀的道長。 經常有香客問我披蕉,道長颈畸,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,791評論 1 295
  • 正文 為了忘掉前任没讲,我火速辦了婚禮眯娱,結果婚禮上,老公的妹妹穿的比我還像新娘食零。我一直安慰自己困乒,他們只是感情好,可當我...
    茶點故事閱讀 67,794評論 6 392
  • 文/花漫 我一把揭開白布贰谣。 她就那樣靜靜地躺著娜搂,像睡著了一般。 火紅的嫁衣襯著肌膚如雪吱抚。 梳的紋絲不亂的頭發(fā)上百宇,一...
    開封第一講書人閱讀 51,631評論 1 305
  • 那天,我揣著相機與錄音秘豹,去河邊找鬼携御。 笑死,一個胖子當著我的面吹牛既绕,可吹牛的內容都是我干的啄刹。 我是一名探鬼主播,決...
    沈念sama閱讀 40,362評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼凄贩,長吁一口氣:“原來是場噩夢啊……” “哼誓军!你這毒婦竟也來了?” 一聲冷哼從身側響起疲扎,我...
    開封第一講書人閱讀 39,264評論 0 276
  • 序言:老撾萬榮一對情侶失蹤昵时,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后椒丧,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體壹甥,經...
    沈念sama閱讀 45,724評論 1 315
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 37,900評論 3 336
  • 正文 我和宋清朗相戀三年壶熏,在試婚紗的時候發(fā)現(xiàn)自己被綠了句柠。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 40,040評論 1 350
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖溯职,靈堂內的尸體忽然破棺而出管怠,到底是詐尸還是另有隱情,我是刑警寧澤缸榄,帶...
    沈念sama閱讀 35,742評論 5 346
  • 正文 年R本政府宣布,位于F島的核電站祝拯,受9級特大地震影響甚带,放射性物質發(fā)生泄漏。R本人自食惡果不足惜佳头,卻給世界環(huán)境...
    茶點故事閱讀 41,364評論 3 330
  • 文/蒙蒙 一鹰贵、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧康嘉,春花似錦碉输、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,944評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至肄梨,卻和暖如春阻荒,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背众羡。 一陣腳步聲響...
    開封第一講書人閱讀 33,060評論 1 270
  • 我被黑心中介騙來泰國打工侨赡, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人粱侣。 一個月前我還...
    沈念sama閱讀 48,247評論 3 371
  • 正文 我出身青樓羊壹,卻偏偏與公主長得像,于是被迫代替她去往敵國和親齐婴。 傳聞我的和親對象是個殘疾皇子油猫,可洞房花燭夜當晚...
    茶點故事閱讀 44,979評論 2 355

推薦閱讀更多精彩內容