掃描下方二維碼或者微信搜索公衆號
菜鳥飛呀
飛,便可關注微信公衆號,閱讀更多Spring源碼分析
和Java併發編程
文章。java
在閱讀本文以前能夠先思考一下如下兩個問題node
1.
ReentrantLock是如何在Java層面(非JVM層面)實現鎖的?2.
什麼是公平鎖?什麼是非公平鎖?Lock
是JUC包下的一個接口,裏面定義了獲取鎖、釋放鎖等和鎖相關的方法,ReentrantLock
是Lock接口的一個具體實現類,它的功能是可重入地獨佔式地獲取鎖。這裏有兩個概念,可重入
和獨佔式
。可重入表示的是同一個線程能屢次獲取到鎖。獨佔式表示的是,同一時刻只能有一個線程獲取到鎖。ReentrantLock
實現的鎖又能夠分爲兩類,分別是公平鎖
和非公平鎖
,分別由ReentrantLock類中的兩個內部類FairSync
和NonfairSync
來實現。FiarSync和NonfairSync均繼承了Sync類,而Sync類又繼承了AbstractQueuedSynchronizer(AQS)類,因此ReentrantLock最終是依靠AQS來實現鎖的。關於AQS的知識能夠參考如下兩篇文章。/** * Creates an instance of {@code ReentrantLock}. * This is equivalent to using {@code ReentrantLock(false)}. */
public ReentrantLock() {
sync = new NonfairSync();
}
/** * Creates an instance of {@code ReentrantLock} with the * given fairness policy. * * @param fair {@code true} if this lock should use a fair ordering policy */
public ReentrantLock(boolean fair) {
sync = fair ? new FairSync() : new NonfairSync();
}
複製代碼
lock()
和unlock()
方法實現是由FairSync類中的lock()
和release()
方法實現的(其中release()方法定義在AQS中)。public static void main(String[] args) {
ReentrantLock lock = new ReentrantLock(true);
try{
lock.lock();
System.out.println("Hello World");
}finally {
lock.unlock();
}
}
複製代碼
static final class FairSync extends Sync {
final void lock() {
// 調用acquire()獲取同步狀態
acquire(1);
}
}
複製代碼
在AQS的acquire()方法中會先調用子類的tryAcquire()方法,此時因爲咱們建立的是公平鎖,因此會調用FairSync類中的tryAcquire()方法。(關於acquire()方法的詳細介紹,能夠參考:隊列同步器(AQS)源碼分析)。編程
tryAcquire()方法的做用就是獲取同步狀態(也就是獲取鎖),若是當前線程成功獲取到鎖,那麼就會將AQS中的同步狀態state加1,而後返回true,若是沒有獲取到鎖,將會返回false。FairSync類上tryAcquire()方法的源碼以下。設計模式
protected final boolean tryAcquire(int acquires) {
final Thread current = Thread.currentThread();
// 獲取同步狀態state的值(在AQS中,state就至關於鎖,若是線程能成功修改state的值,那麼就表示該線程獲取到了鎖)
int c = getState();
if (c == 0) {
// 若是c等於0,表示尚未任何線程獲取到鎖
/** * 此時可能存在多個線程同時執行到這兒,均知足c==0這個條件。 * 在if條件中,會先調用hasQueuedPredecessors()方法來判斷隊列中是否已經有線程在排隊,該方法返回true表示有線程在排隊,返回false表示沒有線程在排隊 * 第1種狀況:hasQueuedPredecessors()返回true,表示有線程排隊, * 此時 !hasQueuedPredecessors() == false,因爲&& 運算符的短路做用,if的條件判斷爲false,那麼就不會進入到if語句中,tryAcquire()方法就會返回false * * 第2種狀況:hasQueuedPredecessors()返回false,表示沒有線程排隊 * 此時 !hasQueuedPredecessors() == true, 那麼就會進行&&後面的判斷,就會調用compareAndSetState()方法去進行修改state字段的值 * compareAndSetState()方法是一個CAS方法,它會對state字段進行修改,它的返回值結果又須要分兩種狀況 * 第 i 種狀況:對state字段進行CAS修改爲功,就會返回true,此時if的條件判斷就爲true了,就會進入到if語句中,同時也表示當前線程獲取到了鎖。那麼最終tryAcquire()方法會返回true * 第 ii 種狀況:若是對state字段進行CAS修改失敗,說明在這一瞬間,已經有其餘線程獲取到了鎖,那麼if的條件判斷就爲false了,就不會進入到if語句塊中,最終tryAcquire()方法會返回false。 */
if (!hasQueuedPredecessors() &&
compareAndSetState(0, acquires)) {
// 當前線程成功修改了state字段的值,那麼就表示當前線程獲取到了鎖,那麼就將AQS中鎖的擁有者設置爲當前線程,而後返回true。
setExclusiveOwnerThread(current);
return true;
}
}
// 若是c等於0,則表示已經有線程獲取到了鎖,那麼這個時候,就須要判斷獲取到鎖的線程是否是當前線程
else if (current == getExclusiveOwnerThread()) {
// 若是是當前線程,那麼就將state的值加1,這就是鎖的重入
int nextc = c + acquires;
if (nextc < 0)
throw new Error("Maximum lock count exceeded");
// 由於此時確定只有一個線程獲取到了鎖,只有獲取到鎖的線程纔會執行到這行代碼,因此能夠直接調用setState(nextc)方法來修改state的值,這兒不會存在線程安全的問題。
setState(nextc);
// 而後返回true,表示當前線程獲取到了鎖
return true;
}
// 若是state不等於0,且當前線程也等於已經獲取到鎖的線程,那麼就返回false,表示當前線程沒有獲取到鎖
return false;
}
}
複製代碼
1.
若是等於0,就表示目前尚未線程持有到鎖,那麼這個時候就會先調用hasQueuedPredecessors()
方法判斷同步隊列中有沒有等待獲取鎖的線程,若是有線程在排隊,那麼當前線程確定獲取鎖失敗(由於AQS的設計的原則是FIFO,既然前面有人已經在排隊了,你就不能插隊,老老實實去後面排隊去),那麼tryAcquire()方法會返回false。若是同步隊列中沒有線程排隊,那麼就讓當前線程對state進行CAS操做,若是設置成功,就表示當前獲取到鎖了,返回true;若是CAS失敗,表示在這一瞬間,鎖被其餘線程搶走了,那麼當前線程就獲取鎖失敗,就返回false。2.
若是不等於0,就表示已經有線程獲取到鎖了,那麼此時就會去判斷當前線程是否是等於已經持有鎖的線程(getExclusiveOwnerThread()方法的做用就是返回已經持有鎖的線程
),若是相等,就讓state加1,這就是所謂的重入鎖,重入鎖就是容許同一個線程屢次獲取到鎖。3.
state既不等於0,當前線程也不等於持有鎖的線程,那麼就返回false,表示當前線程獲取到鎖失敗。public final boolean hasQueuedPredecessors() {
Node t = tail; // Read fields in reverse initialization order
Node h = head;
Node s;
/** * 該方法的做用是判斷隊列中是否有線程在排隊,返回true表示有線程排隊,返回false表示沒有線程排隊 * * 首先判斷 h!=t,即判斷頭結點和尾結點是否相等,頭結點和尾結點相等只有兩種狀況, * 第一:隊列尚未被初始化,此時head = null, tail = null,此時線程不須要排隊。 * 第二:已經有一個線程獲取到了鎖,當第二個線程進來獲取鎖時,由於獲取不到鎖,因此會被須要入隊,入隊以前它須要先初始化隊列, * 在初始化時,在enq()方法中,第一步是先將tail = head,而後第二步再將當前線程表明的Node設置爲tail。因此在這兩步操做 * 的中間的一瞬間,是存在tail = head這個可能性的。此時對全部線程而言是也不須要排隊 * 當 h!=t 的結果爲true時,接下來會判斷((s = h.next) == null || s.thread != Thread.currentThread()),先判斷(s = h.next) == null * * 判斷(s = h.next) == null 時,先令 s = h.next ,表示獲取到頭結點的的下一個節點,即第二個節點,若是 s == null 則表示第二個節點爲null,因爲前面已經判斷了h!=t, * 說明此時已經有第二個線程進入到了隊列中,只不過它還沒來及將head節點的next指針的指向修改,因此此時線程線程須要排隊, * 由於||運算的短路緣由,當(s = h.next) == null 的結果爲true時,就不會進入到後面的判斷了,而此時hasQueuedPredecessors()會返回true,表示線程須要排隊。 * 當s不爲null時,會進行s.thread != Thread.currentThread() 的判斷 * 它會判斷s節點中的線程是否不等於當前線程,若是不等於當前線程,hasQueuedPredecessors()會返回true,說明當前線程須要排隊。 * 由於當第二個節點不是當前線程,那麼就說明當前線程應該至少是排在隊列中的第三位,那麼它須要排隊。 * 若是s節點中的線程等於當前線程,那麼說明當前線程是排在隊列中的第二位(第一位是已經獲取到鎖的線程),此時線程是不須要排隊的, * 由於可能在這一瞬間已經獲取到鎖的線程釋放了鎖,那麼排在隊列中第二位的線程還排啥子隊哦,直接去嘗試獲取鎖便可。 * * * */
return h != t &&
((s = h.next) == null || s.thread != Thread.currentThread());
}
複製代碼
public final boolean release(int arg) {
if (tryRelease(arg)) {
// 當釋放鎖成功之後,須要喚醒同步隊列中的其餘線程
Node h = head;
// 當waitStatus!=0時,表示同步隊列中還有其餘線程在等待獲取鎖
if (h != null && h.waitStatus != 0)
// 喚醒同步隊列中下一個能嘗試獲取鎖的線程
unparkSuccessor(h);
return true;
}
return false;
}
複製代碼
protected final boolean tryRelease(int releases) {
int c = getState() - releases;
// 判斷當前線程是否是持有鎖的線程,若是不是就拋出異常
if (Thread.currentThread() != getExclusiveOwnerThread())
throw new IllegalMonitorStateException();
// 由於上面一步已經確認了是當前線程持有鎖,因此在修改state時,確定是線程安全的
boolean free = false;
// 由於可能鎖被重入了,重入了幾回就須要釋放幾回鎖,因此這個地方須要判斷,只有當state=0時才表示徹底釋放了鎖。
if (c == 0) {
free = true;
setExclusiveOwnerThread(null);
}
setState(c);
return free;
}
複製代碼
LockSupport.unpark()
。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. */
Node s = node.next;
if (s == null || s.waitStatus > 0) {
s = null;
/** * 同步隊列中的節點鎖表明的線程,可能被取消了,此時這個節點的waitStatus=1 * 所以這個時候利用for循環從同步隊列尾部開始向前遍歷,判斷節點是否是被取消了 * 正常狀況下,當頭結點釋放鎖之後,應該喚醒同步隊列中的第二個節點,可是若是第二個節點的線程被取消了,此時就不能喚醒它了, * 就應該判斷第三個節點,若是第三個節點也被取消了,再依次日後判斷,直到第一次出現沒有被取消的節點。若是都被取消了,此時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);
}
複製代碼
static final class NonfairSync extends Sync {
final void lock() {
// 先嚐試獲取鎖,若是獲取鎖失敗,就再去調用AQS的acquire()方法
if (compareAndSetState(0, 1))
setExclusiveOwnerThread(Thread.currentThread());
else
acquire(1);
}
}
複製代碼
protected final boolean tryAcquire(int acquires) {
// 調用nonfairTryAcquire()方法
return nonfairTryAcquire(acquires);
}
複製代碼
final boolean nonfairTryAcquire(int acquires) {
final Thread current = Thread.currentThread();
int c = getState();
if (c == 0) {
// 與公平鎖的不一樣之處在於,公平在在進行CAS操做以前,會先判斷同步隊列中是否有人排隊。
// !hasQueuedPredecessors()
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;
}
複製代碼
c == 0
時,非公平鎖沒有去判斷同步隊列中是否有人在排隊
,而是直接
調用CAS方法去設置state的值。而公平鎖則是先判斷同步隊列中是否有人排隊
,若是沒有人排隊,才調用CAS方法去設置state的值。即非公平鎖沒有調用hasQueuedPredecessors()
方法。其餘的邏輯,公平鎖與非公平鎖的邏輯就同樣了。ReentrantLock
來建立公平鎖和非公平鎖,經過默認的無參構造方法建立的是非公平鎖;當使用有參構造器建立時,若是參數傳入的是false,則建立的是非公平鎖,若是傳入的是true,則建立的是公平鎖。FairSync
,實現非公平鎖的同步組件的類是NonfairSync
,它們都是隊列同步器AQS的子類。tryAcquire()
方法,分析了公平鎖和非公平鎖的獲取鎖的流程。同時對比二者的實現代碼,發現非公平鎖在獲取鎖時不會判斷同步隊列中有沒有線程在排隊,而是直接嘗試去修改state的值,而公平鎖在獲取鎖時,會先判斷同步隊列中有沒有線程在排隊,若是沒有才會嘗試去修改state的值。1.
ReentrantLock是如何在Java層面(非JVM實現)實現鎖的?組合一個同步組件Sync來實現鎖
,這個同步組件繼承了AQS,並重寫了AQS中的tryAcquire()、tryRelease()等方法,最終實際上仍是經過AQS以及重寫的tryAcquire()、tryRelease()等方法來實現鎖的邏輯。ReentrantLock實現鎖的方式,也是JUC包下其餘類型的鎖的實現方法,經過組合一個自定義的同步組件,這個同步組件須要繼承AQS,而後重寫AQS中的部分方法便可實現一把自定義的鎖,一般這個同步組件被定義成內部類。2.
什麼是公平鎖?什麼是非公平鎖?FairSync
同步組件實現的鎖是公平鎖,它獲取鎖的原則是,在同步隊列中等待時間最長的線程獲取鎖,所以稱它爲公平鎖。由NonfairSync
同步組件實現的鎖是非公平鎖,它獲取鎖的原則是,同步隊列外的線程在嘗試獲取鎖時,不會判斷隊列中有沒有線程在排隊,而是上來就搶,搶到鎖了就走,搶不到了纔去排隊,所以稱它爲不公平的。