Java并發(fā)編程之淺談ReentrantLock
一、首先看圖
二、lock()跟蹤源碼
這里對(duì)公平鎖和非公平鎖做了不同實(shí)現(xiàn),由構(gòu)造方法參數(shù)決定是否公平。
public ReentrantLock(boolean fair) { sync = fair ? new FairSync() : new NonfairSync(); }
2.1 非公平鎖實(shí)現(xiàn)
static final class NonfairSync extends Sync { private static final long serialVersionUID = 7316153563782823691L; final void lock() { if (compareAndSetState(0, 1)) setExclusiveOwnerThread(Thread.currentThread()); else acquire(1); } protected final boolean tryAcquire(int acquires) { return nonfairTryAcquire(acquires); } }
代碼量很少。首先compareAndSetState(0, 1)
通過CAS(期望值0,新值1,內(nèi)存值stateOffset)
- 如果修改成功,即搶占到鎖,
setExclusiveOwnerThread(Thread.currentThread());
將AQS中的變量exclusiveOwnerThread
設(shè)置為當(dāng)前搶占到鎖的線程,也就是圖中的ThreadA。 - 若沒有搶占成功,證明此時(shí)鎖被占用,執(zhí)行方法
acquire(1);
。
public final void acquire(int arg) { if (!tryAcquire(arg) && acquireQueued(addWaiter(Node.EXCLUSIVE), arg)) selfInterrupt(); }
這里主要看兩個(gè)方法tryAcquire(arg)
和acquireQueued(addWaiter(Node.EXCLUSIVE), arg)
。當(dāng)滿足if條件后,會(huì)給當(dāng)前線程標(biāo)記一個(gè)interrupt
狀態(tài)。
2.1.1 tryAcquire(arg)
這個(gè)方法又有多個(gè)實(shí)現(xiàn)。這里看NonfairSync
非公平鎖。
protected final boolean tryAcquire(int acquires) { return nonfairTryAcquire(acquires); } 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; }
在這個(gè)方法中,還不死心,首先會(huì)判斷下AQS中的state是否為0,為0也就是說距離上次嘗試獲取鎖到現(xiàn)在準(zhǔn)備進(jìn)入隊(duì)列(雙向鏈表)中這段時(shí)間內(nèi),鎖已經(jīng)被釋放,可以重新CAS嘗試獲取鎖。
如果當(dāng)前鎖還是被持有狀態(tài),就是state!=0
,就會(huì)判斷,當(dāng)前線程是不是當(dāng)前持有鎖的線程exclusiveOwnerThread
,如果是,則state+1
,從這里可以看出state表示的是重入次數(shù)。
全部不滿足,返回false。
2.1.2 acquireQueued(addWaiter(Node.EXCLUSIVE), arg)
addWaiter
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; if (compareAndSetTail(pred, node)) { pred.next = node; return node; } } enq(node); return node; }
tryAcquire(arg)返回false,證明當(dāng)前線程還是沒有獲取到鎖。那么就要進(jìn)入隊(duì)列等待了,首先addWaiter
方法,將當(dāng)前線程封裝成一個(gè)Node,如果pred不為空,則將當(dāng)前節(jié)點(diǎn)做鏈表的尾部插入,同時(shí)為了防止在此期間前序節(jié)點(diǎn)已經(jīng)不在隊(duì)列中了,也會(huì)運(yùn)用CAS操作來執(zhí)行(期望值pred,新值node,內(nèi)存值tailOffset)。
如果前序節(jié)點(diǎn)為空,或者在CAS時(shí)發(fā)現(xiàn)前序節(jié)點(diǎn)已經(jīng)不存在了,則重新構(gòu)建鏈表,將當(dāng)前節(jié)點(diǎn)封裝的Node,加入到鏈表當(dāng)中。
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; } } } }
加入完成后,返回當(dāng)前node節(jié)點(diǎn),進(jìn)入acquireQueued
方法。
acquireQueued
final boolean acquireQueued(final Node node, int arg) { boolean failed = true; try { boolean interrupted = false; for (;;) { //獲取到當(dāng)前node節(jié)點(diǎn)的上一個(gè)節(jié)點(diǎn) final Node p = node.predecessor(); //如果當(dāng)前的上個(gè)節(jié)點(diǎn)就是頭節(jié)點(diǎn),會(huì)再次嘗試獲取鎖 if (p == head && tryAcquire(arg)) { //獲取成功,將當(dāng)前節(jié)點(diǎn)置空,并成為新的頭節(jié)點(diǎn) setHead(node); //這個(gè)p已經(jīng)沒用了,防止內(nèi)存泄漏,直接指向null,下次GC時(shí)回收 p.next = null; // help GC //不需要取消 failed = false; //return false,不需要中斷當(dāng)前線程 return interrupted; } if (shouldParkAfterFailedAcquire(p, node) && parkAndCheckInterrupt()) interrupted = true; } } finally { if (failed) cancelAcquire(node); } }
這里是一個(gè)自旋操作,首先拿到當(dāng)前線程封裝節(jié)點(diǎn)的上一個(gè)節(jié)點(diǎn),如果滿足第一個(gè)if條件if (p == head && tryAcquire(arg))
,證明上個(gè)節(jié)點(diǎn)為頭節(jié)點(diǎn),則此時(shí)當(dāng)前線程也會(huì)再次嘗試獲取鎖,獲取鎖成功,證明此時(shí)沒有別的線程在隊(duì)列中了,則將當(dāng)前node清空并設(shè)置為頭節(jié)點(diǎn),返回不需要中斷當(dāng)前線程。
在第二個(gè)if條件中if (shouldParkAfterFailedAcquire(p, node) && parkAndCheckInterrupt())
。走到這里證明當(dāng)前線程不是第一個(gè)線程節(jié)點(diǎn),或者沒有搶占到鎖,shouldParkAfterFailedAcquire
這個(gè)方法見名知意,在搶占失敗后是否需要park阻塞,里面主要是用于清理雙向鏈表中被取消的節(jié)點(diǎn)線程和未被阻塞的節(jié)點(diǎn)線程。
private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) { int ws = pred.waitStatus;//獲取前置節(jié)點(diǎn)的等待狀態(tài) if (ws == Node.SIGNAL) //前置節(jié)點(diǎn)的等待狀態(tài)為-1,表示前置節(jié)點(diǎn)在隊(duì)列中阻塞,那么當(dāng)前節(jié)點(diǎn)也需要被阻塞在隊(duì)列中 return true; if (ws > 0) { //前置節(jié)點(diǎn)等待狀態(tài)大于0,此前置節(jié)點(diǎn)已經(jīng)被取消,循環(huán)遍歷清除所有已被取消的節(jié)點(diǎn)。 do { node.prev = pred = pred.prev; } while (pred.waitStatus > 0); pred.next = node; } else { //前置節(jié)點(diǎn)等待狀態(tài)小于等于0,且不等于-1,也就是沒有被阻塞也沒有被取消 //則將前置節(jié)點(diǎn)設(shè)置為阻塞狀態(tài)。 compareAndSetWaitStatus(pred, ws, Node.SIGNAL); } return false; }
- 前置節(jié)點(diǎn)的等待狀態(tài)為-1,表示前置節(jié)點(diǎn)在隊(duì)列中阻塞,那么當(dāng)前節(jié)點(diǎn)也需要被阻塞在隊(duì)列中
- 前置節(jié)點(diǎn)等待狀態(tài)大于0,此前置節(jié)點(diǎn)已經(jīng)被取消,循環(huán)遍歷清除所有已被取消的節(jié)點(diǎn)。
- 前置節(jié)點(diǎn)等待狀態(tài)小于等于0,且不等于-1,也就是沒有被阻塞也沒有被取消。則將前置節(jié)點(diǎn)設(shè)置為阻塞狀態(tài)。
到這里,基于非公平鎖的實(shí)現(xiàn)結(jié)束。
2.2 公平鎖實(shí)現(xiàn)
公平鎖和樂觀鎖的區(qū)別就在于,非公平鎖acquire(1)
前會(huì)先嘗試獲取鎖,公平鎖直接acquire(1)
。
static final class FairSync extends Sync { private static final long serialVersionUID = -3000897897090466540L; final void lock() { acquire(1); } }
2.2.1 tryAcquire(arg)
在tryAcquire中也和非公平鎖有一定的區(qū)別。在當(dāng)前鎖沒有被占有時(shí)。非公平鎖不用考慮目前AQS隊(duì)列中的排隊(duì)情況,直接通過CAS嘗試獲取鎖。公平鎖會(huì)看目前隊(duì)列的狀態(tài),再來決定是嘗試占有鎖還是在隊(duì)列中等待。
protected final boolean tryAcquire(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; }
到此這篇關(guān)于Java并發(fā)編程之淺談ReentrantLock的文章就介紹到這了,更多相關(guān)Java ReentrantLock內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
SpringBoot集成mybatis連接oracle的圖文教程
這篇文章主要介紹了Spring Boot集成mybatis連接oracle的圖文教程,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2022-02-02如何使用stream從List對(duì)象中獲取某列數(shù)據(jù)
這篇文章主要介紹了如何使用stream從List對(duì)象中獲取某列數(shù)據(jù)問題,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2022-12-12查看SpringBoot和JDK版本對(duì)應(yīng)關(guān)系的方法
在進(jìn)行一些自主學(xué)習(xí)的時(shí)候,發(fā)現(xiàn)使用maven方式創(chuàng)建的SpringBoot項(xiàng)目啟動(dòng)失敗,最終發(fā)現(xiàn)是SpringBoot版本和JDK版本不對(duì)應(yīng)導(dǎo)致的,所以本文就給大家介紹了如何查看SpringBoot和JDK版本的對(duì)應(yīng)關(guān)系,需要的朋友可以參考下2024-03-03Java中使用@CrossOrigin和Proxy解決跨域問題詳解
這篇文章主要介紹了Java中使用@CrossOrigin和Proxy解決跨域問題詳解,在Web開發(fā)中,如果前端頁面和后端接口不在同一個(gè)域名下,就會(huì)發(fā)生跨域請(qǐng)求的問題,同源策略是瀏覽器的一種安全策略,它限制了來自不同源的客戶端腳本在瀏覽器中運(yùn)行時(shí)的交互,需要的朋友可以參考下2023-12-12SpringBoot定時(shí)任務(wù)動(dòng)態(tài)擴(kuò)展ScheduledTaskRegistrar詳解
這篇文章主要為大家介紹了SpringBoot定時(shí)任務(wù)動(dòng)態(tài)擴(kuò)展ScheduledTaskRegistrar類示例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2023-01-01shiro與spring集成基礎(chǔ)Hello案例詳解
這篇文章主要介紹了shiro與spring集成基礎(chǔ)Hello案例詳解,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下2019-11-11Spring學(xué)習(xí)筆記之bean的基礎(chǔ)知識(shí)
ean在Spring和SpringMVC中無所不在,將這個(gè)概念內(nèi)化很重要,所以下面這篇文章主要給大家介紹了關(guān)于Spring學(xué)習(xí)筆記之bean基礎(chǔ)的相關(guān)資料,文中通過示例代碼介紹的非常詳解,需要的朋友可以參考下。2017-12-12