ReentrantLock

簡單介紹

ReentrantLock 是一個可重入的獨占鎖

  • 可重入
    同一線程外層函數(shù)獲得鎖之后,內(nèi)層遞歸函數(shù)仍然可以獲取該鎖的代碼
    該特性帶來的兩個問題:
    • 如何識別獲取鎖的線程是否為當(dāng)前占據(jù)鎖的線程
    • 線程重復(fù) n 次獲取了鎖纬凤,需要釋放 n 次鎖饺窿,否則會導(dǎo)致別的線程無法獲得鎖
  • 獨占
    一次只能被一個線程所持有
類型
private final Sync sync;

ReentrantLock 的內(nèi)部類 Sync 繼承了 AQSAbstractQueuedSynchronizer),并且有公平鎖 FairSync 和 非公平鎖 NonfaireSync 兩個字類移斩,ReentrantLock 的獲取與釋放鎖操作都是委托給該同步組件來實現(xiàn)的
(注:該篇文章里所有 AQS 相關(guān)的內(nèi)容會再寫一篇相關(guān)的文章肚医,這里不詳細(xì)介紹)

  • 公平鎖
    是指當(dāng)鎖可用時,在鎖上等待時間最長的線程將獲取鎖的使用權(quán)(先來先得)
    使用有參構(gòu)造方法向瓷,傳入 true 創(chuàng)建公平鎖
    public ReentrantLock(boolean fair) {
        sync = fair ? new FairSync() : new NonfairSync();
    }
    
  • 非公平鎖
    隨機分配使用權(quán)肠套,使用 ReentrantLock 無參的構(gòu)造函數(shù),默認(rèn)創(chuàng)建的是非公平鎖
    public ReentrantLock() {
        sync = new NonfairSync();
    }
    
常用方法介紹
  • lock() 方法獲取鎖猖任,如果獲取不到鎖你稚,則當(dāng)前線程在獲取到鎖之前都不可調(diào)度(不響應(yīng)中斷)
    public void lock() {
        sync.lock();
    }
    
  • lockInterruptibly() 方法獲取鎖,則當(dāng)前線程在獲取到鎖之前都不可調(diào)度朱躺,除非有其他線程中斷了當(dāng)前線程(響應(yīng)中斷)
    public void lockInterruptibly() throws InterruptedException {
        sync.acquireInterruptibly(1);
    }
    
  • tryLock() 方法獲取鎖刁赖,如果調(diào)用的時候能夠獲取鎖,那么就獲取鎖并且返回 true长搀,如果當(dāng)前的鎖無法獲取到宇弛,那么這個方法會立刻返回 false
    public boolean tryLock() {
        return sync.nonfairTryAcquire(1);
    }
    
  • tryLock(long timeout, TimeUnit unit) 方法獲取鎖,在指定時間內(nèi)嘗試獲取鎖源请。如果可以獲取鎖枪芒,則獲取鎖并返回 true 彻况,如果無法獲取鎖,則當(dāng)前線程變?yōu)椴豢烧{(diào)度舅踪,除非當(dāng)前線程被中斷或者到了指定的等待時間
    public boolean tryLock(long timeout, TimeUnit unit) throws InterruptedException {
        return sync.tryAcquireNanos(1, unit.toNanos(timeout));
    }
    
  • unlock() 釋放鎖纽甘,釋放當(dāng)前線程占用的鎖。注意:獲取了幾次鎖抽碌,就要釋放幾次鎖
    public void unlock() {
        sync.release(1);
    }
    
  • newCondition() 方法悍赢,返回一個與當(dāng)前的鎖關(guān)聯(lián)的條件變量。在使用這個條件變量之前货徙,當(dāng)前線程必須占用鎖左权。調(diào)用 Conditionawait 方法,會在等待之前原子地釋放鎖破婆,并在等待被喚醒后原子的獲取鎖
    public Condition newCondition() {
        return sync.newCondition();
    }
    
  • isHeldByCurrentThread() 方法涮总,查詢當(dāng)前線程是否保持鎖定
    public boolean isHeldByCurrentThread() {
        return sync.isHeldExclusively();
    }
    
  • isLocked() 方法胸囱,查詢該鎖是否已經(jīng)被鎖定
    public boolean isLocked() {
        return sync.isLocked();
    }
    
  • isFair() 方法祷舀,判斷鎖是公平鎖還是非公平鎖
    public final boolean isFair() {
        return sync instanceof FairSync;
    }
    
Sync 內(nèi)部類
abstract static class Sync extends AbstractQueuedSynchronizer {
  ...
}

Sync 是一個抽象類型,它繼承 AbstractQueuedSynchronizer烹笔,這個 AbstractQueuedSynchronizer 是一個模板類裳扯,它實現(xiàn)了許多和鎖相關(guān)的功能,并提供了鉤子方法供用戶實現(xiàn)谤职,比如 tryAcquire饰豺,tryRelease

static final class NonfairSync extends Sync {
    final void lock() {
        ...
    }
}
static final class FairSync extends Sync {
    final void lock() {
        ...
    }
}

NonfairSyncFairSync 兩個類繼承自 Sync ,實現(xiàn)了 lock 方法

  • lock
    當(dāng)我們調(diào)用 ReentrantLocklock 方法的時候允蜈,實際上是調(diào)用了 NonfairSync 或者 FairSynclock 方法
    • NonfairSync 非公平鎖的 lock 實現(xiàn)
      final void lock() {
          if (compareAndSetState(0, 1))
              setExclusiveOwnerThread(Thread.currentThread());
          else
              acquire(1);
      }
      
    這個方法先用 CAS 操作冤吨,去嘗試搶占該鎖。如果成功饶套,就把當(dāng)前線程設(shè)置在這個鎖上漩蟆,表示搶占成功。如果失敗妓蛮,則調(diào)用 acquire 模板方法怠李,等待搶占
    acquire 方法里調(diào)用了 tryAcquire(int arg) 方法,NonfairSynctryAcquire 實際上又調(diào)用的 SyncnonfairTryAcquire 方法蛤克,如下
    final boolean nonfairTryAcquire(int acquires) {
        final Thread current = Thread.currentThread();
        int c = getState();
        if (c == 0) { // 判斷鎖的狀態(tài)是不是 0捺癞,如果是,則嘗試去原子搶占這個鎖
            if (compareAndSetState(0, acquires)) { // 如果搶占到了构挤,把狀態(tài)設(shè)置為1
                setExclusiveOwnerThread(current); // 并且設(shè)置當(dāng)前線程為獨占線程
                return true;
            }
        }
        else if (current == getExclusiveOwnerThread()) {// 如果鎖的狀態(tài)不為 0髓介,判斷該線程是否是獨占線程(可重入)
            int nextc = c + acquires; // 如果當(dāng)前線程是獨占線程,則增加狀態(tài)變量的值
            if (nextc < 0) // overflow
                throw new Error("Maximum lock count exceeded");
            setState(nextc); // 給狀態(tài)變量賦值
            return true;
        }
        return false;
    }
    
    tryAcquire 一旦返回 false筋现,就會則進入 acquireQueued 流程版保,此段代碼中鎖的獲取可以分為兩種情況:
    1. state為0時:代表鎖已經(jīng)釋放呜笑,可以去獲取,所以使用 CAS 去獲取鎖彻犁,如果獲取成功叫胁,則代表競爭鎖成功,調(diào)用 setExclusiveOwnerThread 設(shè)置當(dāng)前線程為獨占線程汞幢,因為隊列中的線程和新線程都可以 CAS 獲取鎖不需要排隊驼鹅,所以是非公平鎖
    2. status不為0時:代表鎖已經(jīng)被占有,如果當(dāng)前線程是占有鎖的線程(current == getExclusiveOwnerThread()true),更新state森篷,意味著當(dāng)前線程又一次的獲取了鎖输钩,這就是可重入。
    • FairSync 公平鎖的 lock 實現(xiàn)
      final void lock() {
          acquire(1);
      }
      
    acquire 方法里同樣調(diào)用了 tryAcquire(int arg) 方法仲智, FairSynctryAcquire 方法實現(xiàn)如下:
    protected final boolean tryAcquire(int acquires) {
        final Thread current = Thread.currentThread();
        int c = getState();
        if (c == 0) {  // 判斷鎖的狀態(tài)是不是 0买乃,如果是 0,再判斷是否有線程在排隊獲取鎖
            if (!hasQueuedPredecessors() &&  // 如果沒有線程在排隊獲取鎖則嘗試原子搶占鎖
                compareAndSetState(0, acquires)) {  // 如果搶占到了钓辆,把狀態(tài)設(shè)置為1
                setExclusiveOwnerThread(current);  // 并且設(shè)置當(dāng)前線程為獨占線程
                return true;
            }
        }
        else if (current == getExclusiveOwnerThread()) { // 如果鎖的狀態(tài)不為 0剪验,判斷該線程是否是獨占線程(可重入)
            int nextc = c + acquires; // 如果當(dāng)前線程是獨占線程,則增加狀態(tài)變量的值
            if (nextc < 0)
                throw new Error("Maximum lock count exceeded");
            setState(nextc); // 給狀態(tài)變量賦值
            return true;
        }
        return false;
    }
    
    tryAcquire 一旦返回 false前联,就會則進入 acquireQueued 流程功戚,公平鎖獲取鎖的過程與非公平鎖不一樣的地方在 state為0時 新線程需要判斷有沒有線程在排隊獲取鎖,只有當(dāng)沒有的時候才會去嘗試搶占鎖似嗤,如果有線程在排隊啸臀,新線程也會被加入到排隊的隊列中去
  • unlock
    unlock 方法,其實是直接調(diào)用 AbstractQueuedSynchronizerrelease 操作烁落, release 方法先調(diào)用了 tryRelease 方法乘粒,SynctryRelease 方法實現(xiàn)如下:
    protected final boolean tryRelease(int releases) {
        int c = getState() - releases; //狀態(tài)變量值減少,這里是考慮到可重入鎖可能自身會多次占用鎖
        if (Thread.currentThread() != getExclusiveOwnerThread()) // 如果當(dāng)前線程不是獨占線程則拋異常
            throw new IllegalMonitorStateException();
        boolean free = false;
        if (c == 0) { // 當(dāng)狀態(tài)值為 0 伤塌,鎖釋放
            free = true;
            setExclusiveOwnerThread(null); // 將獨占線程設(shè)置為 null
        }
        setState(c); // 狀態(tài)變量賦值
        return free;
    }
    
    一旦 tryRelease 成功灯萍,下一個節(jié)點的線程被喚醒,被喚醒的線程就會進入 acquireQueued 流程中寸谜,去獲取鎖
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末竟稳,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子熊痴,更是在濱河造成了極大的恐慌他爸,老刑警劉巖,帶你破解...
    沈念sama閱讀 216,919評論 6 502
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件果善,死亡現(xiàn)場離奇詭異诊笤,居然都是意外死亡,警方通過查閱死者的電腦和手機巾陕,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,567評論 3 392
  • 文/潘曉璐 我一進店門讨跟,熙熙樓的掌柜王于貴愁眉苦臉地迎上來纪他,“玉大人,你說我怎么就攤上這事晾匠〔杼唬” “怎么了?”我有些...
    開封第一講書人閱讀 163,316評論 0 353
  • 文/不壞的土叔 我叫張陵凉馆,是天一觀的道長薪寓。 經(jīng)常有香客問我,道長澜共,這世上最難降的妖魔是什么向叉? 我笑而不...
    開封第一講書人閱讀 58,294評論 1 292
  • 正文 為了忘掉前任,我火速辦了婚禮嗦董,結(jié)果婚禮上母谎,老公的妹妹穿的比我還像新娘。我一直安慰自己京革,他們只是感情好奇唤,可當(dāng)我...
    茶點故事閱讀 67,318評論 6 390
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著存崖,像睡著了一般冻记。 火紅的嫁衣襯著肌膚如雪睡毒。 梳的紋絲不亂的頭發(fā)上来惧,一...
    開封第一講書人閱讀 51,245評論 1 299
  • 那天,我揣著相機與錄音演顾,去河邊找鬼供搀。 笑死,一個胖子當(dāng)著我的面吹牛钠至,可吹牛的內(nèi)容都是我干的葛虐。 我是一名探鬼主播,決...
    沈念sama閱讀 40,120評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼棉钧,長吁一口氣:“原來是場噩夢啊……” “哼屿脐!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起宪卿,我...
    開封第一講書人閱讀 38,964評論 0 275
  • 序言:老撾萬榮一對情侶失蹤的诵,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后佑钾,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體西疤,經(jīng)...
    沈念sama閱讀 45,376評論 1 313
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,592評論 2 333
  • 正文 我和宋清朗相戀三年休溶,在試婚紗的時候發(fā)現(xiàn)自己被綠了代赁。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片扰她。...
    茶點故事閱讀 39,764評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖芭碍,靈堂內(nèi)的尸體忽然破棺而出徒役,到底是詐尸還是另有隱情,我是刑警寧澤窖壕,帶...
    沈念sama閱讀 35,460評論 5 344
  • 正文 年R本政府宣布廉涕,位于F島的核電站,受9級特大地震影響艇拍,放射性物質(zhì)發(fā)生泄漏狐蜕。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,070評論 3 327
  • 文/蒙蒙 一卸夕、第九天 我趴在偏房一處隱蔽的房頂上張望层释。 院中可真熱鬧,春花似錦快集、人聲如沸贡羔。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,697評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽乖寒。三九已至,卻和暖如春院溺,著一層夾襖步出監(jiān)牢的瞬間楣嘁,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,846評論 1 269
  • 我被黑心中介騙來泰國打工珍逸, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留逐虚,地道東北人。 一個月前我還...
    沈念sama閱讀 47,819評論 2 370
  • 正文 我出身青樓谆膳,卻偏偏與公主長得像叭爱,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子漱病,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 44,665評論 2 354