ReentrantLock

以下分析均基于jdk1.8

AQS 是一個(gè)用于實(shí)現(xiàn)阻塞鎖和相關(guān)同步器的框架,它提供了一些基本的原子操作(如 CAS罩引,自旋等待)以及一個(gè)等待隊(duì)列來(lái)協(xié)調(diào)多個(gè)線程之間的互斥和共享訪問(wèn)各吨。ReentrantLock 實(shí)現(xiàn)了 AQS 的 tryAcquire 和 release 方法來(lái)獲取和釋放鎖,以及 Condition 來(lái)支持鎖的條件等待袁铐。
ReentrantLock 在基于 AQS 實(shí)現(xiàn)的同時(shí)揭蜒,也可以通過(guò)重入鎖的方式實(shí)現(xiàn)線程的可重入性,使得同一個(gè)線程可以多次獲取同一個(gè)鎖而不會(huì)死鎖剔桨。此外屉更,ReentrantLock 還提供了公平鎖和非公平鎖兩種模式,以適應(yīng)不同的應(yīng)用場(chǎng)景洒缀。

AQS

 public final void acquire(int arg) {
        if (!tryAcquire(arg) &&
            acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
            selfInterrupt();
    }

這段代碼是 AQS(AbstractQueuedSynchronizer)類中的 acquire(int arg) 方法實(shí)現(xiàn)瑰谜。該方法用于獲取獨(dú)占式鎖(exclusive lock)欺冀。以下是方法的含義和執(zhí)行流程:

  1. tryAcquire(arg) 嘗試直接獲取獨(dú)占鎖,如果成功返回 true萨脑,否則執(zhí)行第二步隐轩。
  2. addWaiter(Node.EXCLUSIVE) 創(chuàng)建一個(gè)獨(dú)占節(jié)點(diǎn) Node.EXCLUSIVE,將其加入到等待隊(duì)列中渤早,并返回該節(jié)點(diǎn)职车。
  3. acquireQueued(node, arg) 將節(jié)點(diǎn) node 加入到等待隊(duì)列中,并阻塞線程鹊杖,直到獲取獨(dú)占鎖成功為止悴灵。
  4. selfInterrupt() 如果線程在等待過(guò)程中被中斷,調(diào)用 selfInterrupt() 方法將線程的中斷狀態(tài)重新設(shè)置仅淑。

因此称勋,該方法的作用是獲取獨(dú)占式鎖,并在獲取不到鎖時(shí)將線程加入到等待隊(duì)列中涯竟,直到鎖被釋放或者線程被中斷才返回赡鲜。

acquireQueued
    final boolean acquireQueued(final Node node, int arg) {
        boolean failed = true;
        try {
            boolean interrupted = false;
            // 循環(huán)執(zhí)行步驟 1 到步驟 4,直到成功獲取到鎖或者線程被中斷庐船。
            for (;;) {
                // 1. 返回等待隊(duì)列中當(dāng)前節(jié)點(diǎn)的前一個(gè)節(jié)點(diǎn)银酬。
                final Node p = node.predecessor();
                // 2. 如果前一個(gè)節(jié)點(diǎn)是 head(即等待隊(duì)列的首節(jié)點(diǎn))并且能夠通過(guò) tryAcquire(arg) 方法獲取到鎖,就將當(dāng)前節(jié)點(diǎn)設(shè)為新的 head筐钟,將前一個(gè)節(jié)點(diǎn)的 next 引用設(shè)置為 null(以便垃圾回收)揩瞪,然后返回中斷狀態(tài)。
                if (p == head && tryAcquire(arg)) {
                    setHead(node);
                    p.next = null; // help GC
                    failed = false;
                    return interrupted;
                }
                // 3. 如果無(wú)法獲取到鎖篓冲,調(diào)用 shouldParkAfterFailedAcquire(p, node) 判斷當(dāng)前線程是否應(yīng)該掛起等待李破。
                // 如果當(dāng)前線程應(yīng)該掛起等待,則調(diào)用 parkAndCheckInterrupt() 方法將線程掛起并檢查中斷狀態(tài)壹将,如果線程在等待期間被中斷嗤攻,將中斷狀態(tài)設(shè)置為 true。
                if (shouldParkAfterFailedAcquire(p, node) &&
                    parkAndCheckInterrupt())
                    interrupted = true;
            }
        } finally {
            // 如果等待過(guò)程中獲取鎖失敗诽俯,調(diào)用 cancelAcquire(node) 方法取消該節(jié)點(diǎn)的等待狀態(tài)妇菱,并從等待隊(duì)列中移除。
            if (failed)
                cancelAcquire(node);
        }
    }

這段代碼是 AQS(AbstractQueuedSynchronizer)類中的 acquireQueued(Node node, int arg) 方法實(shí)現(xiàn)暴区。該方法用于將節(jié)點(diǎn)加入到等待隊(duì)列中闯团,并且在隊(duì)列中自旋等待直到獲取鎖為止。以下是該方法的主要步驟:

  1. node.predecessor() 返回等待隊(duì)列中當(dāng)前節(jié)點(diǎn)的前一個(gè)節(jié)點(diǎn)仙粱。
  2. 如果前一個(gè)節(jié)點(diǎn)是 head(即等待隊(duì)列的首節(jié)點(diǎn))并且能夠通過(guò) tryAcquire(arg) 方法獲取到鎖房交,就將當(dāng)前節(jié)點(diǎn)設(shè)為新的 head,將前一個(gè)節(jié)點(diǎn)的 next 引用設(shè)置為 null(以便垃圾回收)伐割,然后返回中斷狀態(tài)涌萤。
  3. 如果無(wú)法獲取到鎖淹遵,調(diào)用 shouldParkAfterFailedAcquire(p, node) 判斷當(dāng)前線程是否應(yīng)該掛起等待。
  4. 如果當(dāng)前線程應(yīng)該掛起等待负溪,則調(diào)用 parkAndCheckInterrupt() 方法將線程掛起并檢查中斷狀態(tài)透揣,如果線程在等待期間被中斷,將中斷狀態(tài)設(shè)置為 true川抡。
  5. 循環(huán)執(zhí)行步驟 1 到步驟 4辐真,直到成功獲取到鎖或者線程被中斷。
  6. 如果等待過(guò)程中獲取鎖失敗崖堤,調(diào)用 cancelAcquire(node) 方法取消該節(jié)點(diǎn)的等待狀態(tài)侍咱,并從等待隊(duì)列中移除。

因此密幔,acquireQueued(Node node, int arg) 方法實(shí)現(xiàn)了等待隊(duì)列中的節(jié)點(diǎn)自旋等待獲取鎖楔脯,直到獲取到鎖或者線程被中斷。在等待期間胯甩,會(huì)根據(jù)節(jié)點(diǎn)的前驅(qū)節(jié)點(diǎn)和鎖狀態(tài)來(lái)判斷是否需要掛起等待昧廷,并且通過(guò)循環(huán)不斷重試獲取鎖的操作。

公平鎖

在 JDK 1.8 中偎箫,ReentrantLock 在公平模式下鎖的搶占規(guī)則如下:

  1. 當(dāng)線程嘗試獲取鎖時(shí)木柬,如果鎖當(dāng)前沒(méi)有被其他線程持有,那么該線程將立即獲得鎖并成為鎖的持有者淹办。
  2. 當(dāng)線程嘗試獲取鎖時(shí)眉枕,如果鎖當(dāng)前被其他線程持有,那么該線程將進(jìn)入等待隊(duì)列怜森,以 FIFO(先進(jìn)先出)的順序排隊(duì)等待獲取鎖速挑。
  3. 當(dāng)鎖的持有者釋放鎖時(shí),如果等待隊(duì)列中存在線程副硅,則會(huì)選擇隊(duì)列中的第一個(gè)線程作為下一個(gè)持有者姥宝,并將其從等待隊(duì)列中移除,然后喚醒該線程想许,使其嘗試獲取鎖伶授。
  4. 如果有多個(gè)線程在等待隊(duì)列中等待獲取鎖断序,那么它們將按照先進(jìn)先出的順序進(jìn)行競(jìng)爭(zhēng)流纹,直到其中一個(gè)線程成功獲取鎖為止。

在公平模式下违诗,所有線程將按照其請(qǐng)求鎖的順序進(jìn)行競(jìng)爭(zhēng)漱凝,因此不會(huì)出現(xiàn)饑餓現(xiàn)象,也就是說(shuō)诸迟,沒(méi)有任何線程會(huì)無(wú)限期地等待獲取鎖茸炒。但是愕乎,由于需要維護(hù)等待隊(duì)列,因此在高并發(fā)場(chǎng)景下壁公,公平模式可能會(huì)導(dǎo)致性能下降感论。

lock
final void lock() {
            acquire(1);
        }
tryAcquire
   protected final boolean tryAcquire(int acquires) {
            // 1. 獲取當(dāng)前線程
            final Thread current = Thread.currentThread();
            // 2. 獲取當(dāng)前鎖的狀態(tài)值
            int c = getState();
            // 3. 判斷鎖是否可用
            if (c == 0) {
            // 3.1 如果當(dāng)前鎖的狀態(tài)值為0,說(shuō)明鎖是可用的紊册。如果沒(méi)有排隊(duì)的線程且能夠通過(guò)CAS(compareAndSetState)操作將鎖的狀態(tài)值從0改為acquires比肄,則獲取鎖成功,將當(dāng)前線程設(shè)置為獨(dú)占鎖的線程并返回true囊陡。
                if (!hasQueuedPredecessors() &&
                    compareAndSetState(0, acquires)) {
                    setExclusiveOwnerThread(current);
                    return true;
                }
            }
            // 4. 判斷是否是獨(dú)占鎖的線程芳绩,如果當(dāng)前線程是獨(dú)占鎖的線程,說(shuō)明已經(jīng)擁有了鎖撞反,此時(shí)通過(guò)增加鎖的狀態(tài)值來(lái)實(shí)現(xiàn)可重入妥色,并返回true。
            else if (current == getExclusiveOwnerThread()) {
                int nextc = c + acquires;
                if (nextc < 0)
                    throw new Error("Maximum lock count exceeded");
                setState(nextc);
                return true;
            }
            // 5. 如果無(wú)法獲取鎖遏片,則返回false嘹害。
            return false;
        }
hasQueuedPredecessors

其中對(duì)于hasQueuedPredecessors方法,官方是這樣描述的:

  1. 如果當(dāng)前線程前面有一個(gè)排隊(duì)的線程丁稀,則為 true
  2. 如果當(dāng)前線程位于隊(duì)列的頭部或隊(duì)列為空吼拥,則返回 false
getExclusiveOwnerThread

getExclusiveOwnerThread()是Java中AbstractOwnableSynchronizer(AOS)類的方法,用于獲取當(dāng)前鎖的獨(dú)占線程线衫。該方法通常與可重入鎖(ReentrantLock)結(jié)合使用凿可。
在可重入鎖中,同一個(gè)線程可以多次獲取鎖授账,每次獲取鎖時(shí)枯跑,都會(huì)將鎖的狀態(tài)值加1。如果當(dāng)前線程已經(jīng)獲取了鎖白热,則它就是鎖的獨(dú)占線程敛助。getExclusiveOwnerThread()方法可以獲取當(dāng)前鎖的獨(dú)占線程,如果當(dāng)前鎖沒(méi)有被線程占用屋确,則返回null纳击。
在tryAcquire()方法中,如果當(dāng)前線程已經(jīng)獲取了鎖攻臀,則可以直接對(duì)鎖的狀態(tài)值進(jìn)行修改焕数,而無(wú)需再次獲取鎖。因此刨啸,通過(guò)getExclusiveOwnerThread()方法來(lái)判斷當(dāng)前線程是否已經(jīng)獲取了鎖堡赔。
總的來(lái)說(shuō),getExclusiveOwnerThread()方法的作用是獲取當(dāng)前鎖的獨(dú)占線程设联,可以用于判斷當(dāng)前線程是否已經(jīng)獲取了鎖善已,從而進(jìn)行相應(yīng)的處理灼捂。

非公平鎖

lock
 final void lock() {
             // 1. 嘗試獲取鎖,如果成功换团,設(shè)置當(dāng)前線程為獨(dú)占線程
            if (compareAndSetState(0, 1))
                setExclusiveOwnerThread(Thread.currentThread());
            else
            // 2. 否則調(diào)用AQS的acquire方法悉稠,子類實(shí)現(xiàn)nonfairTryAcquire方法
                acquire(1);
        }
nonfairTryAcquire
  final boolean nonfairTryAcquire(int acquires) {
              // 1. 首先,獲取當(dāng)前線程對(duì)象并獲取當(dāng)前鎖的狀態(tài)值艘包。
            final Thread current = Thread.currentThread();
            int c = getState();
            // 2. 如果當(dāng)前鎖的狀態(tài)值為0偎球,表示當(dāng)前鎖沒(méi)有被其他線程占用,則當(dāng)前線程可以直接獲取鎖辑甜。
            if (c == 0) {
                // 3. cas嘗試獲取鎖衰絮,如果成功,設(shè)置當(dāng)前線程為獨(dú)占鎖磷醋,并且返回true
                if (compareAndSetState(0, acquires)) {
                    setExclusiveOwnerThread(current);
                    return true;
                }
            }
            // 4. 如果當(dāng)前鎖的狀態(tài)值不為0猫牡,但是當(dāng)前線程已經(jīng)獲取了鎖,則可以直接對(duì)鎖的狀態(tài)值進(jìn)行修改邓线,而無(wú)需再次獲取鎖淌友。
            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;
        }

condition

ReentrantLock是一個(gè)可重入的互斥鎖,它提供了比內(nèi)置鎖更高級(jí)的同步功能骇陈。在使用ReentrantLock時(shí)震庭,我們可以通過(guò)調(diào)用它的newCondition()方法創(chuàng)建一個(gè)Condition對(duì)象,來(lái)實(shí)現(xiàn)更加靈活的線程同步你雌。
Condition是在Java 5中引入的一種新的線程同步機(jī)制器联,它提供了await()和signal()等方法,可以用于線程之間的通信和協(xié)調(diào)婿崭。
ReentrantLock的newCondition()方法可以創(chuàng)建一個(gè)與當(dāng)前鎖關(guān)聯(lián)的Condition對(duì)象拨拓。調(diào)用該Condition對(duì)象的await()方法可以使當(dāng)前線程等待,直到另一個(gè)線程調(diào)用該Condition對(duì)象的signal()方法或signalAll()方法喚醒它氓栈。
舉個(gè)例子渣磷,假設(shè)我們有一個(gè)任務(wù)隊(duì)列,多個(gè)線程需要從隊(duì)列中獲取任務(wù)并執(zhí)行授瘦。我們可以使用ReentrantLock來(lái)實(shí)現(xiàn)對(duì)隊(duì)列的同步醋界,并且為每個(gè)線程分配一個(gè)Condition對(duì)象,以便在隊(duì)列為空時(shí)等待任務(wù)的到來(lái)提完。具體實(shí)現(xiàn)可以參考下面的代碼:

import java.util.LinkedList;
import java.util.Queue;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.ReentrantLock;

public class TaskQueue {
    private Queue<String> queue = new LinkedList<>();
    private ReentrantLock lock = new ReentrantLock();
    private Condition notEmpty = lock.newCondition();

    public void addTask(String task) {
        lock.lock();
        try {
            queue.add(task);
            notEmpty.signal(); // 通知等待的線程
        } finally {
            lock.unlock();
        }
    }

    public String getTask() throws InterruptedException {
        lock.lock();
        try {
            while (queue.isEmpty()) {
                notEmpty.await(); // 等待任務(wù)的到來(lái)
            }
            return queue.remove();
        } finally {
            lock.unlock();
        }
    }
}

在這個(gè)示例中形纺,我們使用ReentrantLock和Condition實(shí)現(xiàn)了一個(gè)線程安全的任務(wù)隊(duì)列。當(dāng)任務(wù)隊(duì)列為空時(shí)氯葬,調(diào)用getTask()方法的線程會(huì)等待挡篓,直到其他線程調(diào)用addTask()方法向隊(duì)列中添加任務(wù)并通過(guò)notEmpty.signal()通知它們婉陷。
注意帚称,在使用Condition對(duì)象時(shí)官研,一定要在調(diào)用await()方法前獲得鎖,并在finally塊中釋放鎖闯睹。否則戏羽,如果線程在等待時(shí)被中斷,它會(huì)持有鎖而無(wú)法釋放楼吃,導(dǎo)致其他線程無(wú)法獲取鎖始花。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市孩锡,隨后出現(xiàn)的幾起案子酷宵,更是在濱河造成了極大的恐慌,老刑警劉巖躬窜,帶你破解...
    沈念sama閱讀 211,376評(píng)論 6 491
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件浇垦,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡荣挨,警方通過(guò)查閱死者的電腦和手機(jī)男韧,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,126評(píng)論 2 385
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)默垄,“玉大人此虑,你說(shuō)我怎么就攤上這事】诙В” “怎么了朦前?”我有些...
    開(kāi)封第一講書(shū)人閱讀 156,966評(píng)論 0 347
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)鹃操。 經(jīng)常有香客問(wèn)我况既,道長(zhǎng),這世上最難降的妖魔是什么组民? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 56,432評(píng)論 1 283
  • 正文 為了忘掉前任棒仍,我火速辦了婚禮,結(jié)果婚禮上臭胜,老公的妹妹穿的比我還像新娘莫其。我一直安慰自己,他們只是感情好耸三,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,519評(píng)論 6 385
  • 文/花漫 我一把揭開(kāi)白布乱陡。 她就那樣靜靜地躺著,像睡著了一般仪壮。 火紅的嫁衣襯著肌膚如雪憨颠。 梳的紋絲不亂的頭發(fā)上,一...
    開(kāi)封第一講書(shū)人閱讀 49,792評(píng)論 1 290
  • 那天,我揣著相機(jī)與錄音爽彤,去河邊找鬼养盗。 笑死,一個(gè)胖子當(dāng)著我的面吹牛适篙,可吹牛的內(nèi)容都是我干的往核。 我是一名探鬼主播,決...
    沈念sama閱讀 38,933評(píng)論 3 406
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼嚷节,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼聂儒!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起硫痰,我...
    開(kāi)封第一講書(shū)人閱讀 37,701評(píng)論 0 266
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤衩婚,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后效斑,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體谅猾,經(jīng)...
    沈念sama閱讀 44,143評(píng)論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,488評(píng)論 2 327
  • 正文 我和宋清朗相戀三年鳍悠,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了税娜。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 38,626評(píng)論 1 340
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡藏研,死狀恐怖敬矩,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情蠢挡,我是刑警寧澤弧岳,帶...
    沈念sama閱讀 34,292評(píng)論 4 329
  • 正文 年R本政府宣布,位于F島的核電站业踏,受9級(jí)特大地震影響禽炬,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜勤家,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,896評(píng)論 3 313
  • 文/蒙蒙 一腹尖、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧伐脖,春花似錦热幔、人聲如沸。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 30,742評(píng)論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至蠕啄,卻和暖如春场勤,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 31,977評(píng)論 1 265
  • 我被黑心中介騙來(lái)泰國(guó)打工和媳, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留格遭,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 46,324評(píng)論 2 360
  • 正文 我出身青樓窗价,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親叹卷。 傳聞我的和親對(duì)象是個(gè)殘疾皇子撼港,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,494評(píng)論 2 348

推薦閱讀更多精彩內(nèi)容