ReentrantLock
是一種 可重入
的 互斥鎖
,它具備與使用 synchronized
方法和語句所訪問的隱式監視器鎖相同的一些基本行爲和語義,但功能更強大。java
ReentrantLock
將由最近成功得到鎖,而且尚未釋放該鎖的線程所擁有。當鎖沒有被另外一個線程所擁有時,調用 lock 的線程將成功獲取該鎖並返回。若是當前線程已經擁有該鎖,此方法將當即返回。可使用 isHeldByCurrentThread()
和 getHoldCount()
方法來檢查此狀況是否發生。編程
此類的構造方法接受一個可選的 公平
參數。當設置爲 true 時(也是當前 ReentrantLock爲公平鎖的狀況
),在多個線程的爭用下,這些鎖傾向於將訪問權授予等待時間最長的線程。不然此鎖將沒法保證任何特定訪問順序。與採用默認設置(使用不公平鎖)相比,使用公平鎖的程序在許多線程訪問時表現爲很低的整體吞吐量(即速度很慢,經常極其慢),可是在得到鎖和保證鎖分配的均衡性時差別較小。不過要注意的是,公平鎖不能保證線程調度的公平性。所以,使用公平鎖的衆多線程中的一員可能得到多倍的成功機會,這種狀況發生在其餘活動線程沒有被處理而且目前並未持有鎖時。還要注意的是,未定時的 tryLock 方法並無使用公平設置。由於即便其餘線程正在等待,只要該鎖是可用的,此方法就能夠得到成功。多線程
經過上文的簡單介紹後,我相信不少小夥伴仍是一臉懵逼,只知道上文咱們提到了 ReentrantLock
與 synchronized
相比有相同的語義,同時其內部分爲了 公平鎖
與 非公平鎖
兩種鎖的類型,且該鎖是支持 重進入
的。那麼爲了方便你們理解這些知識點,咱們先從其類的基本結構講起。具體類結構以下圖所示:架構
從上圖中咱們能夠看出,在 ReentrantLock
類中,定義了三個靜態內部類, Sync 、 FairSync(公平鎖) 、 NonfairSync(非公平鎖 )。其中 Sync
繼承了 AQS(AbstractQueuedSynchronizer)
,而 FairSync
與 NonfairSync
又分別繼承了 Sync
。關於 ReentrantLock
基本類結構以下所示:併發
public class ReentrantLock implements Lock, java.io.Serializable { private final Sync sync; //默認無參構造函數,默認爲非公平鎖 public ReentrantLock() { sync = new NonfairSync(); } //帶參數的構造函數,用戶本身來決定是公平鎖仍是非公平鎖 public ReentrantLock(boolean fair) { sync = fair ? new FairSync() : new NonfairSync(); } //抽象基類繼承AQS,公平鎖與非公平鎖繼承該類,並分別實現其lock()方法 abstract static class Sync extends AbstractQueuedSynchronizer { abstract void lock(); //省略部分代碼.. } //非公平鎖實現 static final class NonfairSync extends Sync {...} //公平鎖實現 static final class FairSync extends Sync {....} //鎖實習,根據具體子類實現調用 public void lock() { sync.lock(); } //響應中斷的獲取鎖 public void lockInterruptibly() throws InterruptedException { sync.acquireInterruptibly(1); } //嘗試獲取鎖,默認採用非公平鎖方法實現 public boolean tryLock() { return sync.nonfairTryAcquire(1); } //超時獲取鎖 public boolean tryLock(long timeout, TimeUnit unit) throws InterruptedException { return sync.tryAcquireNanos(1, unit.toNanos(timeout)); } //釋放鎖 public void unlock() { sync.release(1); } //建立鎖條件(從Condetion來理解,就是建立等待隊列) public Condition newCondition() { return sync.newCondition(); } //省略部分代碼.... } 複製代碼
這裏爲了方便你們理解 ReentrantLock
類的總體結構,我省略了一些代碼及從新排列了一些代碼的順序。ide
從代碼中咱們能夠看出。整個 ReentrantLock
類的實現其實都是交給了其內部 FairSync
與 NonfairSync
兩個類。在 ReentrantLock
類中有兩個構造函數,其中不帶參數的構造函數中默認使用的 NonfairSync(非公平鎖)
。另外一個帶參數的構造函數,用戶本身來決定是 FairSync(公平鎖)
仍是非公平鎖。函數
在上文中,咱們提到了 ReentrantLock
是支持重進入的,那什麼是重進入呢? 重進入是指任意線程在獲取到鎖以後可以再次獲取該鎖,而不會被鎖阻塞
。那接下來咱們看看這個例子,以下所示:post
class ReentrantLockDemo { private static final ReentrantLock lock = new ReentrantLock(); public static void main(String[] args) { Thread thread = new Thread(new Runnable() { @Override public void run() { methodA(); } }); thread.start(); } public static void methodA() { lock.lock(); try { System.out.println("我已經進入methodA方法了"); methodB();//方法A中繼續調用方法B } finally { lock.unlock(); } } public static void methodB() { lock.lock(); try { System.out.println("我已經進入methodB方法了"); } finally { lock.unlock(); } } } //輸出結果 我已經進入methodA方法了 我已經進入methodB方法了 複製代碼
在上述代碼中咱們聲明瞭一個線程調用methodA()方法。同時在該方法內部咱們又調用了methodB()方法。從實際的代碼運行結果來看,當前線程進入方法A以後。在方法B中再次調用 lock.lock();
時,該線程並無被阻塞。也就是說 ReentrantLock
是支持重進入的。那下面咱們就一塊兒來看看其內部的實現原理。學習
由於 ReenTrantLock
將具體實現交給了 NonfairSync(非公平鎖)
與 FairSync(公平鎖)
。同時又由於上述提到的兩個鎖,關於重進入的實現又很是類似。因此這裏將採用 NonfairSync(非公平鎖)
的重進入的實現,來進行分析。但願讀者朋友們閱讀到這裏的時候須要注意,不是我懶哦,是真的很類似哦。ui
好了下面咱們來看代碼。關於NonfairSync代碼以下所示:
static final class NonfairSync extends Sync { final void lock() { if (compareAndSetState(0, 1))////直接獲取同步狀態成功,那麼就再也不走嘗試獲取鎖的過程 setExclusiveOwnerThread(Thread.currentThread()); else acquire(1); } protected final boolean tryAcquire(int acquires) { return nonfairTryAcquire(acquires); } } 複製代碼
當咱們調用lock()方法時,經過CAS操做將AQS中的state的狀態設置爲1,若是成功,那麼表示獲取同步狀態成功。那麼會接着調用 setExclusiveOwnerThread(Thread thread)
方法來設置當前佔有鎖的線程。若是失敗,則調用 acquire(int arg)
方法來獲取同步狀態(該方法是屬於AQS中的獨佔式獲取同步狀態的方法,對該方法不熟悉的小夥伴,建議閱讀 Java併發編程之鎖機制之AQS(AbstractQueuedSynchronizer) )。而該方法內部會調用 tryAcquire(int acquires)
來嘗試獲取同步狀態。經過觀察,咱們發現最終會調用 Sync
類中的 nonfairTryAcquire(int acquires)
方法。咱們繼續跟蹤。
final boolean nonfairTryAcquire(int acquires) { //獲取當前線程 final Thread current = Thread.currentThread(); int c = getState(); //(1)判斷同步狀態,若是未設置,則設置同步狀態 if (c == 0) { if (compareAndSetState(0, acquires)) { setExclusiveOwnerThread(current); return true; } } //(2)若是當前線程已經獲取了同步狀態,則增長同步狀態的值。 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; } 複製代碼
從代碼上來看,該方法主要走兩個步驟,具體以下所示:
也就是說,若是同一個鎖獲取了鎖N( N爲正整數
)次,那麼對應的同步狀態 (state)
也就等於N。那麼接下來的問題來了, 若是當前線程重複N次獲取了鎖,那麼該線程是否須要釋放鎖N次呢?
答案固然是必須的。當咱們調用 ReenTrantLock
的unlock()方法來釋放同步狀態(也就是釋放鎖)時,內部會調用 sync.release(1);
。最終會調用 Sync
類的 tryRelease(int releases)
方法。具體代碼以下所示:
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; } 複製代碼
從代碼中,咱們能夠知道,每調用一次 unlock()
方法會將當前同步狀態減一。也就是說若是當前線程獲取了鎖N次,那麼獲取鎖的相應線程也須要調用 unlock()
方法N次。這也是爲何咱們在以前的重入鎖例子中,爲何 methodB
方法中也要釋放鎖的緣由。
在ReentrantLock中有着 非公平鎖
與 公平鎖
的概念,這裏我先簡單的介紹一下 公平
這兩個字的含義。 這裏的公平是指線程獲取鎖的順序。也就是說鎖的獲取順序是按照當前線程請求的絕對時間順序,固然前提條件下是該線程獲取鎖成功 。
那麼接下來,咱們來分析在ReentrantLock中的非公平鎖的具體實現。
這裏須要你們具有 AQS(AbstractQueuedSynchronizer)
類的相關知識。若是你們不熟悉這塊的知識。建議你們閱讀 Java併發編程之鎖機制之AQS(AbstractQueuedSynchronizer) 。
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); } //省略部分代碼... } 複製代碼
當在ReentrantLock在 非公平鎖的模式下
,去調用lock()方法。那麼接下來最終會走 AQS(AbstractQueuedSynchronizer)
下的 acquire(int arg)(獨佔式的獲取同步狀態)
,也就是以下代碼:
public final void acquire(int arg) { if (!tryAcquire(arg) && acquireQueued(addWaiter(Node.EXCLUSIVE), arg)) selfInterrupt(); } 複製代碼
那麼結合以前咱們所講的AQS知識,在多個線程在 獨佔式
請求共享狀態下(也就是請求鎖)的狀況下,在AQS中的同步隊列中的線程節點狀況以下圖所示:
那麼咱們試想一種狀況,當Nod1中的線程執行完相應任務後,釋放鎖後。這個時候原本該喚醒當前線程節點的 下一個節點
,也就是 Node2中的線程
。這個時候忽然另外一線程忽然來獲取線程(這裏咱們用節點 Node5
來表示)。具體狀況以下圖所示:
那麼根據AQS中獨佔式獲取同步狀態的邏輯。只要 Node5對應的線程獲取同步狀態成功
。那麼就會出現下面的這種狀況,具體狀況以下圖所示:在此我向你們推薦一個架構學習交流裙。交流學習裙號:821169538,裏面會分享一些資深架構師錄製的視頻錄像
從上圖中咱們能夠看出,因爲Node5對象的線程搶佔了獲取同步狀態(獲取鎖)的機會,自己應該被喚醒的 Node2
線程節點。由於獲取同步狀態失敗。因此只有再次的陷入阻塞。那麼綜上。咱們能夠知道。 非公平鎖獲取同步狀態(獲取鎖)時不會考慮同步隊列中中等待的問題。會直接嘗試獲取鎖。也就是會存在後申請,可是會先得到同步狀態(獲取鎖)的狀況。
理解了非公平鎖,再來理解公平鎖就很是簡單了。下面咱們來看一下公平鎖與非公平鎖的加鎖的源碼:
從源碼咱們能夠看出,非公平鎖與公平鎖之間的代碼惟一區別就是多了一個判斷條件 !hasQueuedPredecessors()(圖中紅框所示)
。那咱們查看其源碼(該代碼在AQS中,強烈建議閱讀 Java併發編程之鎖機制之AQS(AbstractQueuedSynchronizer)
)
public final boolean hasQueuedPredecessors() { Node t = tail; Node h = head; Node s; return h != t && ((s = h.next) == null || s.thread != Thread.currentThread()); } 複製代碼
代碼理解理解起來很是簡單,就是判斷當前當前head節點的next節點是否是當前請求同步狀態(請求鎖)的線程。也就是語句 ((s = h.next) == null || s.thread != Thread.currentThread()
。那麼接下來結合AQS中的同步隊列咱們能夠獲得下圖:
那麼綜上咱們能夠得出,公平鎖保證了線程請求的同步狀態(請求鎖)的順序。不會出現另外一個線程搶佔的狀況。