「死磕Java并發(fā)」—–J.U.C之AQS(一篇就夠了)

J.U.C之AQS傳送門

【死磕Java并發(fā)】—–J.U.C之AQS(一篇就夠了) ,作為同步組件的基礎(chǔ),AQS做了太多的工作,自定義同步組件只需要簡單地實(shí)現(xiàn)自定義方法,然后加上AQS提供的模板方法,就可以實(shí)現(xiàn)強(qiáng)大的自定義同步組件,理解了AQS,ReentrantLock理解起來真的是小菜一碟。

ReentrantLock簡介

ReentrantLock,可重入鎖,是一種遞歸無阻塞的同步機(jī)制。它可以等同于synchronized的使用,但是ReentrantLock提供了比synchronized更強(qiáng)大、靈活的鎖機(jī)制,可以減少死鎖發(fā)生的概率。

API介紹如下:

一個可重入的互斥鎖定 Lock,它具有與使用 synchronized 方法和語句所訪問的隱式監(jiān)視器鎖定相同的一些基本行為和語義,但功能更強(qiáng)大。ReentrantLock 將由最近成功獲得鎖定,并且還沒有釋放該鎖定的線程所擁有。當(dāng)鎖定沒有被另一個線程所擁有時,調(diào)用 lock 的線程將成功獲取該鎖定并返回。如果當(dāng)前線程已經(jīng)擁有該鎖定,此方法將立即返回??梢允褂?isHeldByCurrentThread() 和 getHoldCount() 方法來檢查此情況是否發(fā)生。

ReentrantLock還提供了公平鎖和非公平鎖的選擇,構(gòu)造方法接受一個可選的公平參數(shù)(默認(rèn)非公平鎖),當(dāng)設(shè)置為true時,表示公平鎖,否則為非公平鎖。公平鎖與非公平鎖的區(qū)別在于公平鎖的鎖獲取是有順序的。但是公平鎖的效率往往沒有非公平鎖的效率高,在許多線程訪問的情況下,公平鎖表現(xiàn)出較低的吞吐量。

獲取鎖

我們一般都是這么使用ReentrantLock獲取鎖的:

//非公平鎖ReentrantLock lock = new ReentrantLock();lock.lock();

lock方法:

publicvoidlock() {sync.lock();}

Sync為ReentrantLock里面的一個內(nèi)部類,它繼承AQS(AbstractQueuedSynchronizer)。

它有兩個子類:

公平鎖FairSync。

非公平鎖NonfairSync。

ReentrantLock里面大部分的功能都是委托給Sync來實(shí)現(xiàn)的,同時Sync內(nèi)部定義了lock()抽象方法由其子類去實(shí)現(xiàn),默認(rèn)實(shí)現(xiàn)了nonfairTryAcquire(int acquires)方法,可以看出它是非公平鎖的默認(rèn)實(shí)現(xiàn)方式。下面我們看非公平鎖的lock()方法:

final void lock() {//嘗試獲取鎖if (compareAndSetState(0, 1))setExclusiveOwnerThread(Thread.currentThread());else//獲取失敗,調(diào)用AQS的acquire(int arg)方法acquire(1);}

首先會第一次嘗試快速獲取鎖,如果獲取失敗,則調(diào)用acquire(int arg)方法,該方法定義在AQS中,如下:

publicfinalvoidacquire(int arg){if (!tryAcquire(arg) &&acquireQueued(addWaiter(Node.EXCLUSIVE), arg))selfInterrupt();}

這個方法首先調(diào)用tryAcquire(int arg)方法,在AQS中講述過,tryAcquire(int arg)需要自定義同步組件提供實(shí)現(xiàn),非公平鎖實(shí)現(xiàn)如下:

protected final boolean tryAcquire(int acquires) {return nonfairTryAcquire(acquires);}final boolean nonfairTryAcquire(int acquires) {//當(dāng)前線程final Thread current = Thread.currentThread();//獲取同步狀態(tài)int c = getState();//state == 0,表示沒有該鎖處于空閑狀態(tài)if (c == 0) {//獲取鎖成功,設(shè)置為當(dāng)前線程所有if (compareAndSetState(0, acquires)) {setExclusiveOwnerThread(current);return true;}}//線程重入//判斷鎖持有的線程是否為當(dāng)前線程else if (current == getExclusiveOwnerThread()) {int nextc = c + acquires;if (nextc < 0) // overflowthrow new Error("Maximum lock count exceeded");setState(nextc);return true;}return false;}

該方法主要邏輯:首先判斷同步狀態(tài)state == 0 ?,如果是表示該鎖還沒有被線程持有,直接通過CAS獲取同步狀態(tài),如果成功返回true。如果state != 0,則判斷當(dāng)前線程是否為獲取鎖的線程,如果是則獲取鎖,成功返回true。成功獲取鎖的線程再次獲取鎖,這是增加了同步狀態(tài)state。

釋放鎖

獲取同步鎖后,使用完畢則需要釋放鎖,ReentrantLock提供了unlock釋放鎖:

publicvoidunlock(){sync.release(1);}

unlock內(nèi)部使用Sync的release(int arg)釋放鎖,release(int arg)是在AQS中定義的:

publicfinalbooleanrelease(int arg){if (tryRelease(arg)) {Node h = head;if (h != null && h.waitStatus != 0)unparkSuccessor(h);return true;}return false;}

與獲取同步狀態(tài)的acquire(int arg)方法相似,釋放同步狀態(tài)的tryRelease(int arg)同樣是需要自定義同步組件自己實(shí)現(xiàn):

protected final boolean tryRelease(int releases) {//減掉releasesint c = getState() - releases;//如果釋放的不是持有鎖的線程,拋出異常if (Thread.currentThread() != getExclusiveOwnerThread())throw new IllegalMonitorStateException();boolean free = false;//state == 0 表示已經(jīng)釋放完全了,其他線程可以獲取同步狀態(tài)了if (c == 0) {free = true;setExclusiveOwnerThread(null);}setState(c);return free;}

只有當(dāng)同步狀態(tài)徹底釋放后該方法才會返回true。當(dāng)state == 0 時,則將鎖持有線程設(shè)置為null,free= true,表示釋放成功。

公平鎖與非公平鎖

公平鎖與非公平鎖的區(qū)別在于獲取鎖的時候是否按照FIFO的順序來。釋放鎖不存在公平性和非公平性,上面以非公平鎖為例,下面我們來看看公平鎖的tryAcquire(int arg):

protectedfinalbooleantryAcquire(int acquires){final Thread current = Thread.currentThread();int c = getState();if (c == 0) {if (!hasQueuedPredecessors() &&compareAndSetState(0, acquires)) {setExclusiveOwnerThread(current);return true;}}else if (current == getExclusiveOwnerThread()) {int nextc = c + acquires;if (nextc < 0)throw new Error("Maximum lock count exceeded");setState(nextc);return true;}return false;}

比較非公平鎖和公平鎖獲取同步狀態(tài)的過程,會發(fā)現(xiàn)兩者唯一的區(qū)別就在于公平鎖在獲取同步狀態(tài)時多了一個限制條件:hasQueuedPredecessors(),定義如下:

public final boolean hasQueuedPredecessors() {Node t = tail; //尾節(jié)點(diǎn)Node h = head; //頭節(jié)點(diǎn)Node s;//頭節(jié)點(diǎn) != 尾節(jié)點(diǎn)//同步隊(duì)列第一個節(jié)點(diǎn)不為null//當(dāng)前線程是同步隊(duì)列第一個節(jié)點(diǎn)return h != t &&((s = h.next) == null || s.thread != Thread.currentThread());}

該方法主要做一件事情:主要是判斷當(dāng)前線程是否位于CLH同步隊(duì)列中的第一個。如果是則返回true,否則返回false。

ReentrantLock與synchronized的區(qū)別

前面提到ReentrantLock提供了比synchronized更加靈活和強(qiáng)大的鎖機(jī)制,那么它的靈活和強(qiáng)大之處在哪里呢?他們之間又有什么相異之處呢?

首先他們肯定具有相同的功能和內(nèi)存語義。

與synchronized相比,ReentrantLock提供了更多,更加全面的功能,具備更強(qiáng)的擴(kuò)展性。例如:時間鎖等候,可中斷鎖等候,鎖投票。

ReentrantLock還提供了條件Condition,對線程的等待、喚醒操作更加詳細(xì)和靈活,所以在多個條件變量和高度競爭鎖的地方,ReentrantLock更加適合(以后會闡述Condition)。

ReentrantLock提供了可輪詢的鎖請求。它會嘗試著去獲取鎖,如果成功則繼續(xù),否則可以等到下次運(yùn)行時處理,而synchronized則一旦進(jìn)入鎖請求要么成功要么阻塞,所以相比synchronized而言,ReentrantLock會不容易產(chǎn)生死鎖些。

ReentrantLock支持更加靈活的同步代碼塊,但是使用synchronized時,只能在同一個synchronized塊結(jié)構(gòu)中獲取和釋放。注:ReentrantLock的鎖釋放一定要在finally中處理,否則可能會產(chǎn)生嚴(yán)重的后果。

ReentrantLock支持中斷處理,且性能較synchronized會好些。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • 此篇博客所有源碼均來自JDK 1.8 ReentrantLock,可重入鎖,是一種遞歸無阻塞的同步機(jī)制。它可以等同...
    chenssy閱讀 1,146評論 1 11
  • Java并發(fā)編程 來自Java并發(fā)編程的藝術(shù)個人博客: http://blog.csdn.net/qq_22329...
    越長越圓閱讀 3,339評論 4 54
  • 一片樹葉之上, 有一滴淚水, 從早晨滴到晚上, 依然沒有風(fēng)干。 荏苒, 歲月的記憶, 嵌入你刺痛的靈魂。 花落的枝...
    捷悟閱讀 189評論 1 15
  • 很多人說,小學(xué)生,就是一個十分幸福的人。壓力小,朋友多,作業(yè)少??荚嚂r還可以作個弊。 他們錯了。小學(xué)時光,有六年。...
    汐子醬閱讀 247評論 0 0
  • 雨后晴空,走在被昨夜雨水洗滌的石板路上,耳畔伴著鳥兒喧鬧的嘰喳,沐著和煦的陽光……忽然生出一顆如雨后晴空般清朗的心...
    流年多遠(yuǎn)閱讀 356評論 0 0

友情鏈接更多精彩內(nèi)容