欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

一文帶你掌握Java?ReentrantLock加解鎖原理

 更新時間:2022年12月02日 09:21:55   作者:阿笠在健身  
這篇文章將為大家詳細介紹一下Java中ReentrantLock?加鎖和釋放鎖的原理,以及和?Synchronized?的對比。文中的示例代碼講解詳細,希望對大家有所幫助

簡要總結(jié) ReentrantLock

實現(xiàn)原理:volatile 變量 + CAS設(shè)置值 + AQS + 兩個隊列

實現(xiàn)阻塞:同步隊列 + CAS搶占標(biāo)記為 valatile 的 state

實現(xiàn)等待喚醒:await :持有鎖,park ->加入等待隊列 ;signal:喚醒下一個等待隊列節(jié)點,轉(zhuǎn)移進入同步隊列,然后CAS搶占或者按照阻塞隊列等待搶占。接著 await 后續(xù)內(nèi)容程序得以繼續(xù)執(zhí)行。

ReentrantLock 結(jié)構(gòu)分析

ReentrantLock 繼承了Lock接口, lock方法實際上是調(diào)用了Sync的子類NonfairSync(非公平鎖)的lock方法。ReentrantLock的真正實現(xiàn)在他的兩個內(nèi)部類NonfairSync 和 FairSync中,默認實現(xiàn)是非公平鎖。并且內(nèi)部類都繼承于內(nèi)部類Sync,而Sync根本的實現(xiàn)則是大名鼎鼎的 AbstractQueuedSynchronizer 同步器(AQS)。

具體詳見如下代碼:

public?class?ReentrantLock?implements?Lock,?java.io.Serializable?{
????private?static?final?long?serialVersionUID?=?7373984872572414699L;
????/**?Synchronizer?providing?all?implementation?mechanics?*/
????private?final?Sync?sync;
??
??public?ReentrantLock()?{
????????sync?=?new?NonfairSync();
????}
?????abstract?static?class?Sync?extends?AbstractQueuedSynchronizer?{
???????……省略代碼
?????}
??
??//非公平鎖
??static?final?class?NonfairSync?extends?Sync?{
????????private?static?final?long?serialVersionUID?=?7316153563782823691L;

????????/**
?????????*?Performs?lock.??Try?immediate?barge,?backing?up?to?normal
?????????*?acquire?on?failure.
?????????*/
????????final?void?lock()?{
????????????if?(compareAndSetState(0,?1))
????????????????setExclusiveOwnerThread(Thread.currentThread());
????????????else
????????????????acquire(1);
????????}

????????protected?final?boolean?tryAcquire(int?acquires)?{
????????????return?nonfairTryAcquire(acquires);
????????}
????}
??//?公平鎖
??static?final?class?FairSync?extends?Sync?{
????????private?static?final?long?serialVersionUID?=?-3000897897090466540L;

????????final?void?lock()?{
????????????acquire(1);
????????}

????????/**
?????????*?Fair?version?of?tryAcquire.??Don't?grant?access?unless
?????????*?recursive?call?or?no?waiters?or?is?first.
?????????*/
????????protected?final?boolean?tryAcquire(int?acquires)?{
……省略
????????}
????}
??//?lock?方法本質(zhì)就是調(diào)用sync類
??public?void?lock()?{
????????sync.lock();
????}
}

lock 加鎖過程

按照調(diào)用 lock 方法是否搶占鎖成功,可以以調(diào)用 park 方法為界限,將加鎖的過程分為兩部分:一部分是當(dāng)前線程被阻塞前,另一部分是線程被喚醒繼續(xù)執(zhí)行后。(這里以非公平鎖為例)

阻塞前

1.直接通過CAS嘗試獲取鎖,設(shè)置state為1。如果獲取成功則將鎖標(biāo)識設(shè)為獨占,就是是將當(dāng)前線程設(shè)置給 exclusiveOwnerThread。

final?void?lock()?{
????if?(compareAndSetState(0,?1))
????????setExclusiveOwnerThread(Thread.currentThread());
????else
????????acquire(1);
}

2.如果獲取失敗,再次嘗試獲取,調(diào)用acquire。

3.tryAcquire ->:判斷鎖是否被占有,如果空閑則再次嘗試CAS獲取鎖;如果已被占有則對比占有鎖的線程是否為本線程,是的話將state+1,這就是可重入鎖的關(guān)鍵邏輯。

//AbstractQueuedSynchronizer
public?final?void?acquire(int?arg)?{
????if?(!tryAcquire(arg)?&&
????????acquireQueued(addWaiter(Node.EXCLUSIVE),?arg))
????????selfInterrupt();
}
//ReentrantLock.NonfairSync
protected?final?boolean?tryAcquire(int?acquires)?{
????return?nonfairTryAcquire(acquires);
}
//ReentrantLock.Sync
final?boolean?nonfairTryAcquire(int?acquires)?{
????final?Thread?current?=?Thread.currentThread();
????int?c?=?getState();
????if?(c?==?0)?{
??????//?cas再次嘗試獲取
????????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;
}

4.如果獲取失敗則將節(jié)點插入隊列尾部,如果隊列為空,則會初始化隊列,并且設(shè)置頭尾節(jié)點為空節(jié)點,再將Node設(shè)為尾節(jié)點。

//?獲取鎖失敗
acquireQueued(addWaiter(Node.EXCLUSIVE),?arg))

//?加入同步隊列
private?Node?addWaiter(Node?mode)?{
????Node?node?=?new?Node(Thread.currentThread(),?mode);
????//?Try?the?fast?path?of?enq;?backup?to?full?enq?on?failure
????Node?pred?=?tail;
????if?(pred?!=?null)?{
????????node.prev?=?pred;
??????//?通過CAS設(shè)置尾節(jié)點為當(dāng)前節(jié)點,前驅(qū)節(jié)點為之前的尾節(jié)點。
????????if?(compareAndSetTail(pred,?node))?{
????????????pred.next?=?node;
????????????return?node;
????????}
????}
??//?如果當(dāng)前鏈表為空,則在此處進行初始化
????enq(node);
????return?node;
}
private?Node?enq(final?Node?node)?{
????for?(;;)?{
????????Node?t?=?tail;
????????if?(t?==?null)?{?//?Must?initialize
????????????if?(compareAndSetHead(new?Node()))
????????????????tail?=?head;
????????}?else?{
??????????//?追加到隊列尾
????????????node.prev?=?t;
????????????if?(compareAndSetTail(t,?node))?{
????????????????t.next?=?node;
????????????????return?t;
????????????}
????????}
????}
}

5.將新建的Node傳入acquireQueued,獲取前驅(qū)節(jié)點,如果節(jié)點就是head 頭節(jié)點,那么嘗試CAS競爭鎖(head隨時釋放)。如果搶占成功將頭節(jié)點設(shè)為自己。

final?boolean?acquireQueued(final?Node?node,?int?arg)?{
????boolean?failed?=?true;
????try?{
????????boolean?interrupted?=?false;
????????for?(;;)?{
????????????final?Node?p?=?node.predecessor();
??????????//?如果是頭節(jié)點,再次嘗試
????????????if?(p?==?head?&&?tryAcquire(arg))?{
????????????????setHead(node);
????????????????p.next?=?null;?//?help?GC
????????????????failed?=?false;
????????????????return?interrupted;
????????????}
????????????if?(shouldParkAfterFailedAcquire(p,?node)?&&
????????????????parkAndCheckInterrupt())
????????????????interrupted?=?true;
????????}
????}?finally?{
????????if?(failed)
????????????cancelAcquire(node);
????}
}

6.如果沒有搶占成功,則進入shouldParkAfterFailedAcquire邏輯,將前驅(qū)節(jié)點設(shè)置為Signal,表示后繼節(jié)點(也就是當(dāng)前節(jié)點)需要前驅(qū)節(jié)點去喚醒。設(shè)置完之后再次進入自旋鎖,嘗試獲得鎖。

關(guān)于Node的狀態(tài)這里說明一下:

節(jié)點剛創(chuàng)建的時候,status=0,假設(shè)這時候本節(jié)點就是head節(jié)點,那么他會進入else邏輯,將自身狀態(tài)設(shè)置為Signal,然后再次進入自旋,嘗試獲取鎖。如果還是沒有獲取到鎖,那么再次進入shouldParkAfterFailedAcquire方法后會進入第一個if邏輯,方法返回True。

/**
*?Checks?and?updates?status?for?a?node?that?failed?to?acquire.
*?Returns?true?if?thread?should?block.?This?is?the?main?signal
*?control?in?all?acquire?loops.??Requires?that?pred?==?node.prev.
*?如果獲取鎖失敗,檢查并且更新節(jié)點。如果需要被park阻塞,返回true。
*?在所有的循環(huán)邏輯中,這是主要的信號控制邏輯。
*
* pred:表示前驅(qū)節(jié)點
* node:表示當(dāng)前線程節(jié)點
*/
private?static?boolean?shouldParkAfterFailedAcquire(Node?pred,?Node?node)?{
????int?ws?=?pred.waitStatus;
????if?(ws?==?Node.SIGNAL)
???????//?第二次嘗試獲取鎖會進入這段邏輯
????????/*
?????????*?This?node?has?already?set?status?asking?a?release
?????????*?to?signal?it,?so?it?can?safely?park.
?????????*/
?????????//?表明線程已經(jīng)準(zhǔn)備好被阻塞并等待之后被喚醒
????????return?true;
????if?(ws?>?0)?{
????????/*
?????????*?Predecessor?was?cancelled.?Skip?over?predecessors?and
?????????*?indicate?retry.
?????????*/
?????????//?若pred.waitStatus狀態(tài)位大于0,說明這個前驅(qū)點已經(jīng)取消了獲取鎖的操作,
?????????//?doWhile循環(huán)會遞歸刪除掉這些放棄獲取鎖的節(jié)點
????????do?{
????????????node.prev?=?pred?=?pred.prev;
????????}?while?(pred.waitStatus?>?0);
????????pred.next?=?node;
????}?else?{
????????/*
?????????*?節(jié)點剛創(chuàng)建的時候,status=0,邏輯會走到這里將自身狀態(tài)設(shè)置為signal
?????????*?waitStatus?must?be?0?or?PROPAGATE.??Indicate?that?we
?????????*?need?a?signal,?but?don't?park?yet.??Caller?will?need?to
?????????*?retry?to?make?sure?it?cannot?acquire?before?parking.
?????????*/
?????????//若狀態(tài)位不為Node.SIGNAL,且沒有取消操作,則會嘗試將前驅(qū)節(jié)點狀態(tài)位修改為Node.SIGNAL
????????//?表示將會喚醒后繼節(jié)點
????????compareAndSetWaitStatus(pred,?ws,?Node.SIGNAL);
????}
????return?false;
}

7.第二次自旋獲取失敗后,由于前驅(qū)節(jié)點已經(jīng)是Signal,這時進入parkAndCheckInterrupt,將當(dāng)前線程阻塞,等待被喚醒。后續(xù)其他線程如果也嘗試搶占鎖,會同樣被阻塞。

private?final?boolean?parkAndCheckInterrupt()?{
??//?阻塞線程
????LockSupport.park(this);
??//?線程繼續(xù)執(zhí)行
????return?Thread.interrupted();
}

park方法被喚醒后

在其他線程釋放鎖資源后,喚醒下一個節(jié)點,park的后半部分邏輯繼續(xù)執(zhí)行。

1.繼續(xù)執(zhí)行之前Park之后的邏輯,在此處線程被喚醒。這里會返回中斷標(biāo)記,這也是為什么ReentrantLock可以相應(yīng)中斷的原因。

2.然后再次進入自旋鎖,使用CAS獲取到鎖標(biāo)記,將頭節(jié)點設(shè)為當(dāng)前節(jié)點,然后返回中斷標(biāo)記跳出循環(huán)。

3.至此,獲取鎖流程結(jié)束。

unlock 釋放鎖過程

1.嘗試釋放鎖,用state減去1,判斷是否等于0。如果等于0表示已經(jīng)完全釋放鎖,將線程標(biāo)記設(shè)為null。否則釋放失敗,表示當(dāng)前線程仍在繼續(xù)持有,繼續(xù)持有說明有重入情況。

//?ReentrantLock
public?void?unlock()?{
????sync.release(1);
}
//?AQS
public?final?boolean?release(int?arg)?{
??//?釋放鎖
????if?(tryRelease(arg))?{
????????Node?h?=?head;
????????if?(h?!=?null?&&?h.waitStatus?!=?0)
??????????//?喚醒后繼節(jié)點
????????????unparkSuccessor(h);
????????return?true;
????}
????return?false;
}
//?釋放鎖
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;
}

2.拿到頭節(jié)點,然后解鎖后繼節(jié)點。如果當(dāng)前節(jié)點狀態(tài)小于0(signal=-1),則修改節(jié)點status為0。然后向后遞歸找到status小于等于0的節(jié)點(正常為0),調(diào)用unpark解除阻塞。返回解鎖成功。

//?喚醒后繼節(jié)點
private?void?unparkSuccessor(Node?node)?{
????/*
?????*?If?status?is?negative?(i.e.,?possibly?needing?signal)?try
?????*?to?clear?in?anticipation?of?signalling.??It?is?OK?if?this
?????*?fails?or?if?status?is?changed?by?waiting?thread.
?????*/
????int?ws?=?node.waitStatus;
????if?(ws?<?0)
????????compareAndSetWaitStatus(node,?ws,?0);

????/*
?????*?Thread?to?unpark?is?held?in?successor,?which?is?normally
?????*?just?the?next?node.??But?if?cancelled?or?apparently?null,
?????*?traverse?backwards?from?tail?to?find?the?actual
?????*?non-cancelled?successor.
?????*/
??//?拿到下一個節(jié)點
????Node?s?=?node.next;
??//要解除阻塞的線程在后繼節(jié)點中,通常只是下一個節(jié)點。但如果取消或明顯為空,則從尾部向前遍歷以找到實際未取消的繼任者。
????if?(s?==?null?||?s.waitStatus?>?0)?{
????????s?=?null;
????????for?(Node?t?=?tail;?t?!=?null?&&?t?!=?node;?t?=?t.prev)
????????????if?(t.waitStatus?<=?0)
????????????????s?=?t;
????}
????if?(s?!=?null)
??????//解鎖
????????LockSupport.unpark(s.thread);
}

3.在這之后便繼續(xù)開始執(zhí)行之前被阻塞的線程中的邏輯。

到這里 ReentrantLock 的加解鎖過程原理便講解結(jié)束,關(guān)于條件隊列的內(nèi)容,有興趣后續(xù)文章會做講解。

對比 Synchronized

既然已經(jīng)了解了 ReentrantLock ,那么在此對大家所熟知的 Synchronized 進行一個對比。

與Synchronized相同點

1.ReentrantLock和synchronized都是獨占鎖,只允許線程互斥的訪問臨界區(qū)。

但是實現(xiàn)上兩者不同:synchronized加鎖解鎖的過程是隱式的,用戶不用手動操作,優(yōu)點是操作簡單,但顯得不夠靈活。一般并發(fā)場景使用synchronized的就夠了;ReentrantLock需要手動加鎖和解鎖,且解鎖的操作盡量要放在finally代碼塊中,保證線程正確釋放鎖。ReentrantLock操作較為復(fù)雜,但是因為可以手動控制加鎖和解鎖過程,在復(fù)雜的并發(fā)場景中能派上用場。

2.ReentrantLock和synchronized都是可重入鎖。

synchronized因為可重入因此可以放在被遞歸執(zhí)行的方法上,且不用擔(dān)心線程最后能否正確釋放鎖;而ReentrantLock在重入時要卻確保重復(fù)獲取鎖的次數(shù)必須和重復(fù)釋放鎖的次數(shù)一樣,否則可能導(dǎo)致其他線程無法獲得該鎖。

3.都可以實現(xiàn)線程之間的等待通知機制。使用synchronized結(jié)合Object上的wait和notify方法可以實現(xiàn)線程間的等待通知機制。ReentrantLock結(jié)合Condition接口同樣可以實現(xiàn)這個功能。而且相比前者使用起來更清晰也更簡單。

與Synchronized 不同點

  • ReentrantLock是Java層面的實現(xiàn),synchronized是JVM層面的實現(xiàn)。
  • 使用synchronized關(guān)鍵字實現(xiàn)同步,線程執(zhí)行完同步代碼塊會自動釋放鎖(a 線程執(zhí)行完同步代碼會釋放鎖 ;b 線程執(zhí)行過程中發(fā)生異常會釋放鎖),而ReentrantLock需要手動釋放鎖需在finally中手工釋放鎖(unlock()方法釋放鎖),否則容易造成線程死鎖。
  • synchronized是非公平鎖,ReentrantLock可以實現(xiàn)公平和非公平鎖。
  • ReentrantLock 可以設(shè)置超時獲取鎖。在指定的截止時間之前獲取鎖,如果截止時間到了還沒有獲取到鎖,則返回。配合重試機制更好的解決死鎖。
  • ReentrantLock上等待獲取鎖的線程是可中斷的,線程可以放棄等待鎖。而synchonized會無限期等待下去。
  • ReentrantLock 的 tryLock() 方法可以嘗試非阻塞的獲取鎖,調(diào)用該方法后立刻返回,如果能夠獲取則返回true,否則返回false。
  • synchronized無法判斷是否獲取鎖的狀態(tài),Lock可以判斷是否獲取到鎖,并且可以主動嘗試去獲取鎖。

到此這篇關(guān)于一文帶你掌握Java ReentrantLock加解鎖原理的文章就介紹到這了,更多相關(guān)Java ReentrantLock加解鎖內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!

相關(guān)文章

  • spring boot中interceptor攔截器未生效的解決

    spring boot中interceptor攔截器未生效的解決

    這篇文章主要介紹了spring boot中interceptor攔截器未生效的解決,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教
    2021-09-09
  • Spring Data JPA的Audit功能審計數(shù)據(jù)庫的變更

    Spring Data JPA的Audit功能審計數(shù)據(jù)庫的變更

    數(shù)據(jù)庫審計是指當(dāng)數(shù)據(jù)庫有記錄變更時,可以記錄數(shù)據(jù)庫的變更時間和變更人等,這樣以后出問題回溯問責(zé)也比較方便,本文討論Spring Data JPA審計數(shù)據(jù)庫變更問題,感興趣的朋友一起看看吧
    2021-06-06
  • SpringCloud版本問題報錯及解決方法

    SpringCloud版本問題報錯及解決方法

    這篇文章主要介紹了SpringCloud版本問題報錯及解決方法,文中通過示例代碼介紹的非常詳細,對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友可以參考下
    2019-07-07
  • java -jar后臺啟動的四種方式小結(jié)

    java -jar后臺啟動的四種方式小結(jié)

    這篇文章主要介紹了java -jar后臺啟動的四種方式,具有很好的參考價值,希望對大家有所幫助,如有錯誤或未考慮完全的地方,望不吝賜教
    2023-09-09
  • Java語法基礎(chǔ)之for語句練習(xí)

    Java語法基礎(chǔ)之for語句練習(xí)

    以下是對Java語法基礎(chǔ)中的for語句進行了詳細介紹,需要的朋友可以過來參考下
    2013-07-07
  • java ThreadPoolExecutor 并發(fā)調(diào)用實例詳解

    java ThreadPoolExecutor 并發(fā)調(diào)用實例詳解

    這篇文章主要介紹了java ThreadPoolExecutor 并發(fā)調(diào)用實例詳解的相關(guān)資料,需要的朋友可以參考下
    2017-05-05
  • SpringBoot讀取配置文件的五種方法總結(jié)

    SpringBoot讀取配置文件的五種方法總結(jié)

    這篇文章主要為大家詳細介紹了SpringBoot讀取配置文件的五種方法,文中的示例代碼講解詳細,對我們學(xué)習(xí)SpringBoot有一定幫助,需要的可以參考一下
    2022-08-08
  • Spring Data JPA中的Specification動態(tài)查詢詳解

    Spring Data JPA中的Specification動態(tài)查詢詳解

    Specification是一個設(shè)計模式,用于企業(yè)級應(yīng)用開發(fā)中,其主要目的是將業(yè)務(wù)規(guī)則從業(yè)務(wù)邏輯中分離出來,在數(shù)據(jù)查詢方面,Specification可以定義復(fù)雜的查詢,使其更易于重用和測試,這篇文章主要介紹了Spring Data JPA中的Specification動態(tài)查詢詳解,需要的朋友可以參考下
    2023-07-07
  • 使用JPA雙向多對多關(guān)聯(lián)關(guān)系@ManyToMany

    使用JPA雙向多對多關(guān)聯(lián)關(guān)系@ManyToMany

    這篇文章主要介紹了使用JPA雙向多對多關(guān)聯(lián)關(guān)系@ManyToMany,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教
    2022-06-06
  • spring cloud consul注冊的服務(wù)報錯critical的解決

    spring cloud consul注冊的服務(wù)報錯critical的解決

    這篇文章主要介紹了spring cloud consul注冊的服務(wù)報錯critical的解決,小編覺得挺不錯的,現(xiàn)在分享給大家,也給大家做個參考。一起跟隨小編過來看看吧
    2019-03-03

最新評論