您好,登錄后才能下訂單哦!
ReentrantLock源碼解析是什么,相信很多沒有經驗的人對此束手無策,為此本文總結了問題出現的原因和解決方法,通過這篇文章希望你能解決這個問題。
什么是可重鎖ReentrantLock?
那么什么是重入鎖呢?
重入鎖(遞歸鎖)可以理解為:同一個線程函數獲得鎖之后,內層遞歸函數依然能夠獲取到該鎖對象的代碼,也即,在同一個線程的外層方法訪問的時候,獲取到了鎖,在進入內層方法后能夠自動獲取到鎖。線程可以進入任何一個它已經擁有的鎖所同步著的代碼塊。額,說的啥意思?每個中文都認識,但是組合在一起,就不知道啥意思了。
我們來舉個生活中的例子:
在現實生活中,我們一般只需要帶有自己大門的鑰匙(當然,如果是合租的朋友還需要帶著自己房間的鑰匙)。當我們開了大門的鑰匙,進入房間后,我們在去廚房或者是去衛生間的時候,不用在拿鑰匙開廚房或者衛生間的門了吧。為啥呢?因為我們已經已經有大門的鎖的鑰匙并且已經進入到了房間了。廚房和衛生間已經在大門鎖管理的范圍內了。這種場景站在并發鎖的角度來看的話:一同一個線程函數獲得鎖之后(你拿著鑰匙打開了大門之后),內層遞歸函數依然能夠獲取到該鎖對象的代碼(進入房間后,房間內的廚房衛生間可以隨便出入)。這樣是不是就好理解了?
底層實現原理主要是利用通過繼承AQS來實現的,也是利用通過對volatile state的CAS操作+CLH隊列來實現;
CAS:Compare and Swap 比較并交換。CAS的思想很簡單:3個參數,一個當前內存值V、預期值A,即將更新的值B,當前僅當預期值A和內存值V相等的時候,將內存值V修改為B,否則什么都不做。該操作是一個原子操作被廣泛的用于java的底層實現中,在java中,CAS主要是有sun.misc.Unsafe這個類通過JNI調用CPU底層指令實現;更多底層的思想參考狼哥的文章cas的底層原理:https://www.jianshu.com/p/fb6e91b013cc
CLH隊列:也叫同步隊列,是帶頭結點的雙向非循環列表,是AQS的主要實現原理(結構如下圖所示)
最常用的方式:
int a = 12; //注意:通常情況下,這個會設置成一個類變量,比如說Segement中的段鎖與copyOnWriteArrayList中的全局鎖 final ReentrantLock lock = new ReentrantLock(); lock.lock();//獲取鎖 try { a++;//業務邏輯 } catch (Exception e) { }finally{ lock.unlock();//釋放鎖 }
1、對于ReentrantLock需要掌握以下幾點
ReentrantLock的創建(公平鎖/非公平鎖)
上鎖:lock()
解鎖:unlock()
首先說一下類結構:
ReentrantLock-->Lock
NonfairSync/FairSync-->Sync-->AbstractQueuedSynchronizer-->AbstractOwnableSynchronizer
NonfairSync/FairSync-->Sync是ReentrantLock的三個內部類
Node是AbstractQueuedSynchronizer的內部類
注意:上邊這四條線,對應關系:"子類"-->"父類"
2、ReentrantLock的創建
支持公平鎖(先進來的線程先執行)
支持非公平鎖(后進來的線程也可能先執行)
非公平鎖與非公平鎖的創建
非公平鎖:ReentrantLock()或ReentrantLock(false)
final ReentrantLock lock = new ReentrantLock();
公平鎖:ReentrantLock(true)
final ReentrantLock lock = new ReentrantLock(true)
默認情況下使用非公平鎖。
源代碼如下:
ReentrantLock:
/** 同步器:內部類Sync的一個引用 */ private final Sync sync; /** * 創建一個非公平鎖 */ public ReentrantLock() { sync = new NonfairSync(); } /** * 創建一個鎖 * @param fair true-->公平鎖 false-->非公平鎖 */ public ReentrantLock(boolean fair) { sync = (fair)? new FairSync() : new NonfairSync(); }
上述源代碼中出現了三個內部類Sync/NonfairSync/FairSync,這里只列出類的定義,至于這三個類中的具體的方法會在后續的第一次引用的時候介紹。
Sync/NonfairSync/FairSync類定義:
/* * 該鎖同步控制的一個基類.下邊有兩個子類:非公平機制和公平機制.使用了AbstractQueuedSynchronizer類的 */ static abstract class Sync extends AbstractQueuedSynchronizer /** * 非公平鎖同步器 */ final static class NonfairSync extends Sync /** * 公平鎖同步器 */ final static class FairSync extends Sync
3、非公平鎖的lock()
具體使用方法:
lock.lock();
下面先介紹一下這個總體步驟的簡化版,然后會給出詳細的源代碼,并在源代碼的lock()方法部分給出詳細版的步驟。
簡化版的步驟:(非公平鎖的核心)
基于CAS嘗試將state(鎖數量)從0設置為1
A、如果設置成功,設置當前線程為獨占鎖的線程;
B、如果設置失敗,還會再獲取一次鎖數量,
B1、如果鎖數量為0,再基于CAS嘗試將state(鎖數量)從0設置為1一次,如果設置成功,設置當前線程為獨占鎖的線程;
B2、如果鎖數量不為0或者上邊的嘗試又失敗了,查看當前線程是不是已經是獨占鎖的線程了,如果是,則將當前的鎖數量+1;如果不是,則將該線程封裝在一個Node內,并加入到等待隊列中去。等待被其前一個線程節點喚醒。
源代碼:(再介紹源代碼之前,心里有一個獲取鎖的步驟的總的一個印象,就是上邊這個"簡化版的步驟")
3.1、ReentrantLock:lock()
/** *獲取一個鎖 *三種情況: *1、如果當下這個鎖沒有被任何線程(包括當前線程)持有,則立即獲取鎖,鎖數量==1,之后再執行相應的業務邏輯 *2、如果當前線程正在持有這個鎖,那么鎖數量+1,之后再執行相應的業務邏輯 *3、如果當下鎖被另一個線程所持有,則當前線程處于休眠狀態,直到獲得鎖之后,當前線程被喚醒,鎖數量==1,再執行相應的業務邏輯 */ public void lock() { sync.lock();//調用NonfairSync(非公平鎖)或FairSync(公平鎖)的lock()方法 }
3.2、NonfairSync:lock()
/** * 1)首先基于CAS將state(鎖數量)從0設置為1,如果設置成功,設置當前線程為獨占鎖的線程;-->請求成功-->第一次插隊 * 2)如果設置失敗(即當前的鎖數量可能已經為1了,即在嘗試的過程中,已經被其他線程先一步占有了鎖),這個時候當前線程執行acquire(1)方法 * 2.1)acquire(1)方法首先調用下邊的tryAcquire(1)方法,在該方法中,首先獲取鎖數量狀態, * 2.1.1)如果為0(證明該獨占鎖已被釋放,當下沒有線程在使用),這個時候我們繼續使用CAS將state(鎖數量)從0設置為1,如果設置成功,當前線程獨占鎖;-->請求成功-->第二次插隊;當然,如果設置不成功,直接返回false * 2.2.2)如果不為0,就去判斷當前的線程是不是就是當下獨占鎖的線程,如果是,就將當前的鎖數量狀態值+1(這也就是可重入鎖的名稱的來源)-->請求成功 * * 下邊的流程一句話:請求失敗后,將當前線程鏈入隊尾并掛起,之后等待被喚醒。 * * 2.2.3)如果最后在tryAcquire(1)方法中上述的執行都沒成功,即請求沒有成功,則返回false,繼續執行acquireQueued(addWaiter(Node.EXCLUSIVE), arg)方法 * 2.2)在上述方法中,首先會使用addWaiter(Node.EXCLUSIVE)將當前線程封裝進Node節點node,然后將該節點加入等待隊列(先快速入隊,如果快速入隊不成功,其使用正常入隊方法無限循環一直到Node節點入隊為止) * 2.2.1)快速入隊:如果同步等待隊列存在尾節點,將使用CAS嘗試將尾節點設置為node,并將之前的尾節點插入到node之前 * 2.2.2)正常入隊:如果同步等待隊列不存在尾節點或者上述CAS嘗試不成功的話,就執行正常入隊(該方法是一個無限循環的過程,即直到入隊為止)-->第一次阻塞 * 2.2.2.1)如果尾節點為空(初始化同步等待隊列),創建一個dummy節點,并將該節點通過CAS嘗試設置到頭節點上去,設置成功的話,將尾節點也指向該dummy節點(即頭節點和尾節點都指向該dummy節點) * 2.2.2.1)如果尾節點不為空,執行與快速入隊相同的邏輯,即使用CAS嘗試將尾節點設置為node,并將之前的尾節點插入到node之前 * 最后,如果順利入隊的話,就返回入隊的節點node,如果不順利的話,無限循環去執行2.2)下邊的流程,直到入隊為止 * 2.3)node節點入隊之后,就去執行acquireQueued(final Node node, int arg)(這又是一個無限循環的過程,這里需要注意的是,無限循環等于阻塞,多個線程可以同時無限循環--每個線程都可以執行自己的循環,這樣才能使在后邊排隊的節點不斷前進) * 2.3.1)獲取node的前驅節點p,如果p是頭節點,就繼續使用tryAcquire(1)方法去嘗試請求成功,-->第三次插隊(當然,這次插隊不一定不會使其獲得執行權,請看下邊一條), * 2.3.1.1)如果第一次請求就成功,不用中斷自己的線程,如果是之后的循環中將線程掛起之后又請求成功了,使用selfInterrupt()中斷自己 * (注意p==head&&tryAcquire(1)成功是唯一跳出循環的方法,在這之前會一直阻塞在這里,直到其他線程在執行的過程中,不斷的將p的前邊的節點減少,直到p成為了head且node請求成功了--即node被喚醒了,才退出循環) * 2.3.1.2)如果p不是頭節點,或者tryAcquire(1)請求不成功,就去執行shouldParkAfterFailedAcquire(Node pred, Node node)來檢測當前節點是不是可以安全的被掛起, * 2.3.1.2.1)如果node的前驅節點pred的等待狀態是SIGNAL(即可以喚醒下一個節點的線程),則node節點的線程可以安全掛起,執行2.3.1.3) * 2.3.1.2.2)如果node的前驅節點pred的等待狀態是CANCELLED,則pred的線程被取消了,我們會將pred之前的連續幾個被取消的前驅節點從隊列中剔除,返回false(即不能掛起),之后繼續執行2.3)中上述的代碼 * 2.3.1.2.3)如果node的前驅節點pred的等待狀態是除了上述兩種的其他狀態,則使用CAS嘗試將前驅節點的等待狀態設為SIGNAL,并返回false(因為CAS可能會失敗,這里不管失敗與否,都返回false,下一次執行該方法的之后,pred的等待狀態就是SIGNAL了),之后繼續執行2.3)中上述的代碼 * 2.3.1.3)如果可以安全掛起,就執行parkAndCheckInterrupt()掛起當前線程,之后,繼續執行2.3)中之前的代碼 * 最后,直到該節點的前驅節點p之前的所有節點都執行完畢為止,我們的p成為了頭節點,并且tryAcquire(1)請求成功,跳出循環,去執行。 * (在p變為頭節點之前的整個過程中,我們發現這個過程是不會被中斷的) * 2.3.2)當然在2.3.1)中產生了異常,我們就會執行cancelAcquire(Node node)取消node的獲取鎖的意圖。 */ final void lock() { if (compareAndSetState(0, 1))//如果CAS嘗試成功 setExclusiveOwnerThread(Thread.currentThread());//設置當前線程為獨占鎖的線程 else acquire(1); }
注意:在這個方法中,我列出了一個線程獲取鎖的詳細的過程,自己看注釋。
下面列出NonfairSync:lock()中調用的幾個方法與相關屬性。
3.2.1、AbstractQueuedSynchronizer:鎖數量state屬性+相關方法:
/** * 鎖數量 */ private volatile int state; /** * 獲取鎖數量 */ protected final int getState() { return state; } protected final void setState(int newState) { state = newState; }
注意:state是volatile型的
3.2.2、AbstractOwnableSynchronizer:屬性+setExclusiveOwnerThread(Thread t)
/** * 當前擁有獨占鎖的線程 */ private transient Thread exclusiveOwnerThread; /** * 設置獨占鎖的線程為線程t */ protected final void setExclusiveOwnerThread(Thread t) { exclusiveOwnerThread = t; }
3.2.3、AbstractQueuedSynchronizer:屬性+acquire(int arg)
/** * 獲取鎖的方法 * @param arg */ public final void acquire(int arg) { if (!tryAcquire(arg) && acquireQueued(addWaiter(Node.EXCLUSIVE), arg)) selfInterrupt();//中斷自己 }
在介紹上邊這個方法之前,先要說一下AbstractQueuedSynchronizer的一個內部類Node的整體構造,源代碼如下:
/* * 同步等待隊列(雙向鏈表)中的節點 */ static final class Node { /** 線程被取消了 */ static final int CANCELLED = 1; /** * 如果前驅節點的等待狀態是SIGNAL,表示當前節點將來可以被喚醒,那么當前節點就可以安全的掛起了 * 否則,當前節點不能掛起 */ static final int SIGNAL = -1; /**線程正在等待條件*/ static final int CONDITION = -2; /** * waitStatus value to indicate the next acquireShared should * unconditionally propagate */ static final int PROPAGATE = -3; /** Marker to indicate a node is waiting in shared mode */ static final Node SHARED = new Node(); /** 一個標記:用于表明該節點正在獨占鎖模式下進行等待 */ static final Node EXCLUSIVE = null; //值就是前四個int(CANCELLED/SIGNAL/CONDITION/PROPAGATE),再加一個0 volatile int waitStatus; /**前驅節點*/ volatile Node prev; /**后繼節點*/ volatile Node next; /**節點中的線程*/ volatile Thread thread; /** * Link to next node waiting on condition, or the special value SHARED. * Because condition queues are accessed only when holding in exclusive * mode, we just need a simple linked queue to hold nodes while they are * waiting on conditions. They are then transferred to the queue to * re-acquire. And because conditions can only be exclusive, we save a * field by using special value to indicate shared mode. */ Node nextWaiter; /** * Returns true if node is waiting in shared mode */ final boolean isShared() { return nextWaiter == SHARED; } /** * 返回該節點前一個節點 */ final Node predecessor() throws NullPointerException { Node p = prev; if (p == null) throw new NullPointerException(); else return p; } Node() { // Used to establish initial head or SHARED marker } Node(Thread thread, Node mode) { // 用于addWaiter中 this.nextWaiter = mode; this.thread = thread; } Node(Thread thread, int waitStatus) { // Used by Condition this.waitStatus = waitStatus; this.thread = thread; } }
注意:這里我給出了Node類的完整版,其中部分屬性與方法是在共享鎖的模式下使用的,而我們這里的ReentrantLock是一個獨占鎖,只需關注其中的與獨占鎖相關的部分就好(具體有注釋)
3.3、AbstractQueuedSynchronizer:acquire(int arg)方法中使用到的兩個方法
3.3.1、NonfairSync:tryAcquire(int acquires)
/** * 試著請求成功 */ protected final boolean tryAcquire(int acquires) { return nonfairTryAcquire(acquires); }
Syn:
/** * 非公平鎖中被tryAcquire調用 */ final boolean nonfairTryAcquire(int acquires) { final Thread current = Thread.currentThread();//獲取當前線程 int c = getState();//獲取鎖數量 if (c == 0) {//如果鎖數量為0,證明該獨占鎖已被釋放,當下沒有線程在使用 if (compareAndSetState(0, acquires)) {//繼續通過CAS將state由0變為1,注意這里傳入的acquires為1 setExclusiveOwnerThread(current);//將當前線程設置為獨占鎖的線程 return true; } } else if (current == getExclusiveOwnerThread()) {//查看當前線程是不是就是獨占鎖的線程 int nextc = c + acquires;//如果是,鎖狀態的數量為當前的鎖數量+1 if (nextc < 0) // overflow throw new Error("Maximum lock count exceeded"); setState(nextc);//設置當前的鎖數量 return true; } return false; }
注意:這個方法就完成了"簡化版的步驟"中的"A/B/B1"三步,如果上述的請求不能成功,就要執行下邊的代碼了,
下邊的代碼,用一句話介紹:請求失敗后,將當前線程鏈入隊尾并掛起,之后等待被喚醒。在你看下邊的代碼的時候心里默記著這句話。
3.3.2、AbstractQueuedSynchronizer:addWaiter(Node mode)
/** * 將Node節點加入等待隊列 * 1)快速入隊,入隊成功的話,返回node * 2)入隊失敗的話,使用正常入隊 * 注意:快速入隊與正常入隊相比,可以發現,正常入隊僅僅比快速入隊多而一個判斷隊列是否為空且為空之后的過程 * @return 返回當前要插入的這個節點,注意不是前一個節點 */ private Node addWaiter(Node mode) { Node node = new Node(Thread.currentThread(), mode);//創建節點 /* * 快速入隊 */ Node pred = tail;//將尾節點賦給pred if (pred != null) {//尾節點不為空 node.prev = pred;//將尾節點作為創造出來的節點的前一個節點,即將node鏈接到為節點后 /** * 基于CAS將node設置為尾節點,如果設置失敗,說明在當前線程獲取尾節點到現在這段過程中已經有其他線程將尾節點給替換過了 * 注意:假設有鏈表node1-->node2-->pred(當然是雙鏈表,這里畫成雙鏈表才合適), * 通過CAS將pred替換成了node節點,即當下的鏈表為node1-->node2-->node, * 然后根據上邊的"node.prev = pred"與下邊的"pred.next = node"將pred插入到雙鏈表中去,組成最終的鏈表如下: * node1-->node2-->pred-->node * 這樣的話,實際上我們發現沒有指定node2.next=pred與pred.prev=node2,這是為什么呢? * 因為在之前這兩句就早就執行好了,即node2.next和pred.prev這連個屬性之前就設置好了 */ if (compareAndSetTail(pred, node)) { pred.next = node;//將node放在尾節點上 return node; } } enq(node);//正常入隊 return node; }
AbstractQueuedSynchronizer:enq(final Node node)
/** * 正常入隊 * @param node * @return 之前的尾節點 */ private Node enq(final Node node) { for (;;) {//無限循環,一定要阻塞到入隊成功為止 Node t = tail;//獲取尾節點 if (t == null) { //如果尾節點為null,說明當前等待隊列為空 /*Node h = new Node(); // Dummy header h.next = node; node.prev = h; if (compareAndSetHead(h)) {//根據代碼實際上是:compareAndSetHead(null,h) tail = node; return h; }*/ /* * 注意:上邊注釋掉的這一段代碼是jdk1.6.45中的,在后來的版本中,這一段改成了如下這段 * 基于CAS將新節點(一個dummy節點)設置到頭上head去,如果發現內存中的當前值不是null,則說明,在這個過程中,已經有其他線程設置過了。 * 當成功的將這個dummy節點設置到head節點上去時,我們又將這個head節點設置給了tail節點,即head與tail都是當前這個dummy節點, * 之后有新節點入隊的話,就插入到該dummy之后 */ if (compareAndSetHead(new Node())) tail = head; } else {//這一塊兒的邏輯與快速入隊完全相同 node.prev = t; if (compareAndSetTail(t, node)) {//嘗試將node節點設為尾節點 t.next = node;//將node節點設為尾節點 return t; } } } }
注意:這里就是一個完整的入隊方法,具體邏輯看注釋和ReentrantLock:lock()的注釋部分的相關部分。
3.3.3、AbstractQueuedSynchronizer:acquireQueued(final Node node, int arg)
final boolean acquireQueued(final Node node, int arg) { try { boolean interrupted = false; /* * 無限循環(一直阻塞),直到node的前驅節點p之前的所有節點都執行完畢,p成為了head且node請求成功了 */ for (;;) { final Node p = node.predecessor();//獲取插入節點的前一個節點p /* * 注意: * 1、這個是跳出循環的唯一條件,除非拋異常 * 2、如果p == head && tryAcquire(arg)第一次循環就成功了,interrupted為false,不需要中斷自己 * 如果p == head && tryAcquire(arg)第一次以后的循環中如果執行了掛起操作后才成功了,interrupted為true,就要中斷自己了 */ if (p == head && tryAcquire(arg)) { setHead(node);//當前節點設置為頭節點 p.next = null; return interrupted;//跳出循環 } if (shouldParkAfterFailedAcquire(p, node) && parkAndCheckInterrupt()) interrupted = true;//被中斷了 } } catch (RuntimeException ex) { cancelAcquire(node); throw ex; } }
AbstractQueuedSynchronizer:shouldParkAfterFailedAcquire(Node pred, Node node)
/** * 檢測當前節點是否可以被安全的掛起(阻塞) * @param pred 當前節點的前驅節點 * @param node 當前節點 */ private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) { int ws = pred.waitStatus;//獲取前驅節點(即當前線程的前一個節點)的等待狀態 if (ws == Node.SIGNAL)//如果前驅節點的等待狀態是SIGNAL,表示當前節點將來可以被喚醒,那么當前節點就可以安全的掛起了 return true; /* * 1)當ws>0(即CANCELLED==1),前驅節點的線程被取消了,我們會將該節點之前的連續幾個被取消的前驅節點從隊列中剔除,返回false(即不能掛起) * 2)如果ws<=0&&!=SIGNAL,將當前節點的前驅節點的等待狀態設為SIGNAL */ if (ws > 0) { do { /* * node.prev = pred = pred.prev; * 上邊這句代碼相當于下邊這兩句 * pred = pred.prev; * node.prev = pred; */ node.prev = pred = pred.prev; } while (pred.waitStatus > 0); pred.next = node; } else { /* * 嘗試將當前節點的前驅節點的等待狀態設為SIGNAL * 1/這為什么用CAS,現在已經入隊成功了,前驅節點就是pred,除了node外應該沒有別的線程在操作這個節點了,那為什么還要用CAS?而不直接賦值呢? * (解釋:因為pred可以自己將自己的狀態改為cancel,也就是pred的狀態可能同時會有兩條線程(pred和node)去操作) * 2/既然前驅節點已經設為SIGNAL了,為什么最后還要返回false * (因為CAS可能會失敗,這里不管失敗與否,都返回false,下一次執行該方法的之后,pred的等待狀態就是SIGNAL了) */ compareAndSetWaitStatus(pred, ws, Node.SIGNAL); } return false; }
AbstractQueuedSynchronizer:
private final boolean parkAndCheckInterrupt() { LockSupport.park(this);//掛起當前的線程 return Thread.interrupted();//如果當前線程已經被中斷了,返回true }
以上就是一個線程獲取非公平鎖的整個過程(lock())。
4、公平鎖的lock()
具體用法與非公平鎖一樣
如果掌握了非公平鎖的流程,那么掌握公平鎖的流程會非常簡單,只有兩點不同(最后會講)。
簡化版的步驟:(公平鎖的核心)
獲取一次鎖數量,
B1、如果鎖數量為0,如果當前線程是等待隊列中的頭節點,基于CAS嘗試將state(鎖數量)從0設置為1一次,如果設置成功,設置當前線程為獨占鎖的線程;
B2、如果鎖數量不為0或者當前線程不是等待隊列中的頭節點或者上邊的嘗試又失敗了,查看當前線程是不是已經是獨占鎖的線程了,如果是,則將當前的鎖數量+1;如果不是,則將該線程封裝在一個Node內,并加入到等待隊列中去。等待被其前一個線程節點喚醒。
源代碼:
4.1、ReentrantLock:lock()
/** *獲取一個鎖 *三種情況: *1、如果當下這個鎖沒有被任何線程(包括當前線程)持有,則立即獲取鎖,鎖數量==1,之后被喚醒再執行相應的業務邏輯 *2、如果當前線程正在持有這個鎖,那么鎖數量+1,之后被喚醒再執行相應的業務邏輯 *3、如果當下鎖被另一個線程所持有,則當前線程處于休眠狀態,直到獲得鎖之后,當前線程被喚醒,鎖數量==1,再執行相應的業務邏輯 */ public void lock() { sync.lock();//調用FairSync的lock()方法 }
4.2、FairSync:lock()
final void lock() { acquire(1); }
4.3、AbstractQueuedSynchronizer:acquire(int arg)就是非公平鎖使用的那個方法
4.3.1、FairSync:tryAcquire(int acquires)
/** * 獲取公平鎖的方法 * 1)獲取鎖數量c * 1.1)如果c==0,如果當前線程是等待隊列中的頭節點,使用CAS將state(鎖數量)從0設置為1,如果設置成功,當前線程獨占鎖-->請求成功 * 1.2)如果c!=0,判斷當前的線程是不是就是當下獨占鎖的線程,如果是,就將當前的鎖數量狀態值+1(這也就是可重入鎖的名稱的來源)-->請求成功 * 最后,請求失敗后,將當前線程鏈入隊尾并掛起,之后等待被喚醒。 */ protected final boolean tryAcquire(int acquires) { final Thread current = Thread.currentThread(); int c = getState(); if (c == 0) { if (isFirst(current) && 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; }
最后,如果請求失敗后,將當前線程鏈入隊尾并掛起,之后等待被喚醒,下邊的代碼與非公平鎖一樣。
總結:公平鎖與非公平鎖對比
FairSync:lock()少了插隊部分(即少了CAS嘗試將state從0設為1,進而獲得鎖的過程)
FairSync:tryAcquire(int acquires)多了需要判斷當前線程是否在等待隊列首部的邏輯(實際上就是少了再次插隊的過程,但是CAS獲取還是有的)。
最后說一句,
ReentrantLock是基于AbstractQueuedSynchronizer實現的,AbstractQueuedSynchronizer可以實現獨占鎖也可以實現共享鎖,ReentrantLock只是使用了其中的獨占鎖模式
這一塊兒代碼比較多,邏輯比較復雜,最好在閱讀的過程中,可以拿一根筆畫畫入隊等與數據結構相關的圖
一定要記住"簡化版的步驟",這是整個非公平鎖與公平鎖的核心
看完上述內容,你們掌握 ReentrantLock源碼解析是什么的方法了嗎?如果還想學到更多技能或想了解更多相關內容,歡迎關注億速云行業資訊頻道,感謝各位的閱讀!
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。