ReentrantLock基本介紹
ReentrantLock是一種可重入的互斥鎖孽查,它具有與使用synchronized方法和語句所訪問的隱式監(jiān)視器鎖相同的一些基本行為和語義胳螟,但功能更強大棍郎。
ReentrantLock將由最近成功獲得鎖,并且還沒有釋放該鎖的線程所擁有逆甜。當鎖沒有被另一個線程所擁有時,調用 lock 的線程將成功獲取該鎖并返回。如果當前線程已經擁有該鎖虫给,此方法將立即返回∠辣蹋可以使用isHeldByCurrentThread()和getHoldCount()方法來檢查此情況是否發(fā)生抹估。
此類的構造方法接受一個可選的公平參數(shù)。當設置為 true 時(也是當前ReentrantLock為公平鎖的情況)弄兜,在多個線程的爭用下药蜻,這些鎖傾向于將訪問權授予等待時間最長的線程。否則此鎖將無法保證任何特定訪問順序替饿。與采用默認設置(使用不公平鎖)相比语泽,使用公平鎖的程序在許多線程訪問時表現(xiàn)為很低的總體吞吐量(即速度很慢,常常極其慢)视卢,但是在獲得鎖和保證鎖分配的均衡性時差異較小踱卵。不過要注意的是,公平鎖不能保證線程調度的公平性据过。因此惋砂,使用公平鎖的眾多線程中的一員可能獲得多倍的成功機會,這種情況發(fā)生在其他活動線程沒有被處理并且目前并未持有鎖時绳锅。還要注意的是西饵,未定時的 tryLock 方法并沒有使用公平設置。因為即使其他線程正在等待榨呆,只要該鎖是可用的罗标,此方法就可以獲得成功庸队。
ReentrantLock 類基本結構
通過上文的簡單介紹后,我相信很多小伙伴還是一臉懵逼闯割,只知道上文我們提到了ReentrantLock與synchronized相比有相同的語義彻消,同時其內部分為了公平鎖與非公平鎖兩種鎖的類型,且該鎖是支持重進入的宙拉。那么為了方便大家理解這些知識點宾尚,我們先從其類的基本結構講起。具體類結構如下圖所示:
從上圖中我們可以看出谢澈,在ReentrantLock類中煌贴,定義了三個靜態(tài)內部類,Sync锥忿、FairSync(公平鎖)牛郑、NonfairSync(非公平鎖)。其中Sync繼承了AQS(AbstractQueuedSynchronizer)敬鬓,而FairSync與NonfairSync又分別繼承了Sync淹朋。關于ReentrantLock基本類結構如下所示:
public class ReentrantLock implements Lock, java.io.Serializable {
? ? private final Sync sync;
//默認無參構造函數(shù),默認為非公平鎖
? ? public ReentrantLock() {
? ? ? ? sync = new NonfairSync();
? ? }
//帶參數(shù)的構造函數(shù)钉答,用戶自己來決定是公平鎖還是非公平鎖
? ? public ReentrantLock(boolean fair) {
? ? ? ? sync = fair ? new FairSync() : new NonfairSync();
? ? }
? ? //抽象基類繼承AQS础芍,公平鎖與非公平鎖繼承該類,并分別實現(xiàn)其lock()方法
? ? abstract static class Sync extends AbstractQueuedSynchronizer {
? ? ? ? abstract void lock();
? ? ? ? //省略部分代碼..
? ? }
//非公平鎖實現(xiàn)
? ? static final class NonfairSync extends Sync {...}
? ? //公平鎖實現(xiàn)
? ? static final class FairSync extends Sync {....}
? ? //鎖實習数尿,根據具體子類實現(xiàn)調用
? ? public void lock() {
? ? ? ? sync.lock();
? ? }
//響應中斷的獲取鎖
? ? public void lockInterruptibly() throws InterruptedException {
? ? ? ? sync.acquireInterruptibly(1);
? ? }
//嘗試獲取鎖仑性,默認采用非公平鎖方法實現(xiàn)
? ? 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);
? ? }
? ? //創(chuàng)建鎖條件(從Condetion來理解,就是創(chuàng)建等待隊列)
? ? public Condition newCondition() {
? ? ? ? return sync.newCondition();
? ? }
? ? //省略部分代碼....
}
復制代碼
這里為了方便大家理解ReentrantLock類的整體結構右蹦,我省略了一些代碼及重新排列了一些代碼的順序诊杆。
從代碼中我們可以看出。整個ReentrantLock類的實現(xiàn)其實都是交給了其內部FairSync與NonfairSync兩個類何陆。在ReentrantLock類中有兩個構造函數(shù)刽辙,其中不帶參數(shù)的構造函數(shù)中默認使用的NonfairSync(非公平鎖)。另一個帶參數(shù)的構造函數(shù)甲献,用戶自己來決定是FairSync(公平鎖)還是非公平鎖。
重進入實現(xiàn)
在上文中颂翼,我們提到了ReentrantLock是支持重進入的晃洒,那什么是重進入呢?重進入是指任意線程在獲取到鎖之后能夠再次獲取該鎖朦乏,而不會被鎖阻塞球及。那接下來我們看看這個例子,如下所示:
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中繼續(xù)調用方法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是支持重進入的。那下面我們就一起來看看其內部的實現(xiàn)原理庐氮。
因為ReenTrantLock將具體實現(xiàn)交給了NonfairSync(非公平鎖)與FairSync(公平鎖)语稠。同時又因為上述提到的兩個鎖,關于重進入的實現(xiàn)又非常相似弄砍。所以這里將采用NonfairSync(非公平鎖)的重進入的實現(xiàn)仙畦,來進行分析。希望讀者朋友們閱讀到這里的時候需要注意音婶,不是我懶哦慨畸,是真的很相似哦。
好了下面我們來看代碼衣式。關于NonfairSync代碼如下所示:
static final class NonfairSync extends Sync {
? ? ? ? final void lock() {
? ? ? ? ? ? if (compareAndSetState(0, 1))////直接獲取同步狀態(tài)成功寸士,那么就不再走嘗試獲取鎖的過程
? ? ? ? ? ? ? ? setExclusiveOwnerThread(Thread.currentThread());
? ? ? ? ? ? else
? ? ? ? ? ? ? ? acquire(1);
? ? ? ? }
? ? ? ? protected final boolean tryAcquire(int acquires) {
? ? ? ? ? ? return nonfairTryAcquire(acquires);
? ? ? ? }
? ? }
復制代碼
當我們調用lock()方法時,通過CAS操作將AQS中的state的狀態(tài)設置為1瞳收,如果成功碉京,那么表示獲取同步狀態(tài)成功。那么會接著調用setExclusiveOwnerThread(Thread thread)方法來設置當前占有鎖的線程螟深。如果失敗谐宙,則調用acquire(int arg)方法來獲取同步狀態(tài)(該方法是屬于AQS中的獨占式獲取同步狀態(tài)的方法,對該方法不熟悉的小伙伴界弧,建議閱讀Java并發(fā)編程之鎖機制之AQS(AbstractQueuedSynchronizer))凡蜻。而該方法內部會調用tryAcquire(int acquires)來嘗試獲取同步狀態(tài)。通過觀察垢箕,我們發(fā)現(xiàn)最終會調用Sync類中的nonfairTryAcquire(int acquires)方法划栓。我們繼續(xù)跟蹤。
final boolean nonfairTryAcquire(int acquires) {
? ? //獲取當前線程
? ? ? ? ? ? final Thread current = Thread.currentThread();
? ? ? ? ? ? int c = getState();
? ? ? ? ? ? //(1)判斷同步狀態(tài)条获,如果未設置忠荞,則設置同步狀態(tài)
? ? ? ? ? ? if (c == 0) {
? ? ? ? ? ? ? ? if (compareAndSetState(0, acquires)) {
? ? ? ? ? ? ? ? ? ? setExclusiveOwnerThread(current);
? ? ? ? ? ? ? ? ? ? return true;
? ? ? ? ? ? ? ? }
? ? ? ? ? ? }
? ? ? ? ? ? //(2)如果當前線程已經獲取了同步狀態(tài),則增加同步狀態(tài)的值帅掘。
? ? ? ? ? ? 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;
? ? ? ? }
復制代碼
從代碼上來看委煤,該方法主要走兩個步驟,具體如下所示:
(1)先判斷同步狀態(tài)修档, 如果未曾設置碧绞,則設置同步狀態(tài),并設置當前占有鎖的線程吱窝。
(2)判斷是否是同一線程讥邻,如果當前線程已經獲取了同步狀態(tài)(也就是獲取了鎖)迫靖,那么增加同步狀態(tài)的值。
也就是說兴使,如果同一個鎖獲取了鎖N(N為正整數(shù))次系宜,那么對應的同步狀態(tài)(state)也就等于N。那么接下來的問題來了鲫惶,如果當前線程重復N次獲取了鎖蜈首,那么該線程是否需要釋放鎖N次呢?答案當然是必須的欠母。當我們調用ReenTrantLock的unlock()方法來釋放同步狀態(tài)(也就是釋放鎖)時欢策,內部會調用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()方法會將當前同步狀態(tài)減一六水。也就是說如果當前線程獲取了鎖N次俺孙,那么獲取鎖的相應線程也需要調用unlock()方法N次。這也是為什么我們在之前的重入鎖例子中掷贾,為什么methodB方法中也要釋放鎖的原因睛榄。
非公平鎖
在ReentrantLock中有著非公平鎖與公平鎖的概念,這里我先簡單的介紹一下公平這兩個字的含義想帅。?這里的公平是指線程獲取鎖的順序场靴。也就是說鎖的獲取順序是按照當前線程請求的絕對時間順序,當然前提條件下是該線程獲取鎖成功?港准。
那么接下來旨剥,我們來分析在ReentrantLock中的非公平鎖的具體實現(xiàn)。
這里需要大家具備AQS(AbstractQueuedSynchronizer)類的相關知識浅缸。如果大家不熟悉這塊的知識轨帜。建議大家閱讀Java并發(fā)編程之鎖機制之AQS(AbstractQueuedSynchronizer)。
static final class NonfairSync extends Sync {
? ? ? ? private static final long serialVersionUID = 7316153563782823691L;
? ? ? ? final void lock() {
? ? ? ? ? ? if (compareAndSetState(0, 1))//直接獲取同步狀態(tài)成功衩椒,那么就不再走嘗試獲取鎖的過程
? ? ? ? ? ? ? ? setExclusiveOwnerThread(Thread.currentThread());
? ? ? ? ? ? else
? ? ? ? ? ? ? ? acquire(1);
? ? ? ? }
? ? ? ? //省略部分代碼...
? ? }
復制代碼
當在ReentrantLock在非公平鎖的模式下蚌父,去調用lock()方法。那么接下來最終會走AQS(AbstractQueuedSynchronizer)下的acquire(int arg)(獨占式的獲取同步狀態(tài))毛萌,也就是如下代碼:
public final void acquire(int arg) {
? ? ? ? if (!tryAcquire(arg) &&
? ? ? ? ? ? acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
? ? ? ? ? ? selfInterrupt();
? ? }
復制代碼
那么結合之前我們所講的AQS知識梢什,在多個線程在獨占式請求共享狀態(tài)下(也就是請求鎖)的情況下,在AQS中的同步隊列中的線程節(jié)點情況如下圖所示:
那么我們試想一種情況朝聋,當Nod1中的線程執(zhí)行完相應任務后,釋放鎖后囤躁。這個時候本來該喚醒當前線程節(jié)點的下一個節(jié)點,也就是Node2中的線程冀痕。這個時候突然另一線程突然來獲取線程(這里我們用節(jié)點Node5來表示)荔睹。具體情況如下圖所示:
那么根據AQS中獨占式獲取同步狀態(tài)的邏輯。只要Node5對應的線程獲取同步狀態(tài)成功言蛇。那么就會出現(xiàn)下面的這種情況僻他,具體情況如下圖所示:在此我向大家推薦一個架構學習交流裙。交流學習裙號:821169538腊尚,里面會分享一些資深架構師錄制的視頻錄像
從上圖中我們可以看出吨拗,由于Node5對象的線程搶占了獲取同步狀態(tài)(獲取鎖)的機會,本身應該被喚醒的Node2線程節(jié)點婿斥。因為獲取同步狀態(tài)失敗劝篷。所以只有再次的陷入阻塞。那么綜上民宿。我們可以知道娇妓。非公平鎖獲取同步狀態(tài)(獲取鎖)時不會考慮同步隊列中中等待的問題。會直接嘗試獲取鎖活鹰。也就是會存在后申請哈恰,但是會先獲得同步狀態(tài)(獲取鎖)的情況。
公平鎖
理解了非公平鎖志群,再來理解公平鎖就非常簡單了着绷。下面我們來看一下公平鎖與非公平鎖的加鎖的源碼:
從源碼我們可以看出,非公平鎖與公平鎖之間的代碼唯一區(qū)別就是多了一個判斷條件!hasQueuedPredecessors()(圖中紅框所示)锌云。那我們查看其源碼(該代碼在AQS中荠医,強烈建議閱讀Java并發(fā)編程之鎖機制之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節(jié)點的next節(jié)點是不是當前請求同步狀態(tài)(請求鎖)的線程宾抓。也就是語句((s = h.next) == null || s.thread != Thread.currentThread()子漩。那么接下來結合AQS中的同步隊列我們可以得到下圖:
那么綜上我們可以得出,公平鎖保證了線程請求的同步狀態(tài)(請求鎖)的順序。不會出現(xiàn)另一個線程搶占的情況抢野。