欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

圖解Java?ReentrantLock公平鎖和非公平鎖的實(shí)現(xiàn)

 更新時(shí)間:2022年10月12日 10:00:33   作者:JAVA旭陽  
ReentrantLock是Java并發(fā)中十分常用的一個(gè)類,具備類似synchronized鎖的作用。但是相比synchronized,?它具備更強(qiáng)的能力,同時(shí)支持公平鎖和非公平鎖。本文就來聊聊ReentrantLock公平鎖和非公平鎖的實(shí)現(xiàn),需要的可以參考一下

概述

ReentrantLock是Java并發(fā)中十分常用的一個(gè)類,具備類似synchronized鎖的作用。但是相比synchronized, 它具備更強(qiáng)的能力,同時(shí)支持公平鎖和非公平鎖。

公平鎖: 指多個(gè)線程按照申請(qǐng)鎖的順序來獲取鎖,線程直接進(jìn)入隊(duì)列中排隊(duì),隊(duì)列中的第一個(gè)線程才能獲得鎖。

非公平鎖: 多個(gè)線程加鎖時(shí)直接嘗試獲取鎖,能搶到鎖到直接占有鎖,搶不到才會(huì)到等待隊(duì)列的隊(duì)尾等待。

那ReentrantLock中具體是怎么實(shí)現(xiàn)公平和非公鎖的呢?它們之間又有什么優(yōu)缺點(diǎn)呢?本文就帶大家一探究竟。

RenentrantLock原理概述

上面是RenentrantLock的類結(jié)構(gòu)圖。

  • RenentrantLock實(shí)現(xiàn)了Lock接口,Lock接口提供了鎖的通用api,比如加鎖lock,解鎖unlock等操作。
  • RenentrantLock底層加鎖是通過AQS實(shí)現(xiàn)的,兩個(gè)內(nèi)部類FairSync服務(wù)于公平鎖,NofaireSync服務(wù)于非公平鎖的實(shí)現(xiàn),他們統(tǒng)一繼承自AQS。

ReentrantLock 類 API:

public void lock():獲得鎖

如果鎖沒有被另一個(gè)線程占用,則將鎖定計(jì)數(shù)設(shè)置為 1

如果當(dāng)前線程已經(jīng)保持鎖定,則保持計(jì)數(shù)增加 1

如果鎖被另一個(gè)線程保持,則當(dāng)前線程被禁用線程調(diào)度,并且在鎖定已被獲取之前處于休眠狀態(tài)

public void unlock():嘗試釋放鎖

如果當(dāng)前線程是該鎖的持有者,則保持計(jì)數(shù)遞減

如果保持計(jì)數(shù)現(xiàn)在為零,則鎖定被釋放

如果當(dāng)前線程不是該鎖的持有者,則拋出異常

關(guān)于AQS的原理, 強(qiáng)烈大家閱讀深入淺出理解Java并發(fā)AQS的獨(dú)占鎖模式

非公平鎖實(shí)現(xiàn)

演示

@Test
public void testUnfairLock() throws InterruptedException {
    // 無參構(gòu)造函數(shù),默認(rèn)創(chuàng)建非公平鎖模式
    ReentrantLock reentrantLock = new ReentrantLock();

    for (int i = 0; i < 10; i++) {
        final int threadNum = i;
        new Thread(() -> {
            reentrantLock.lock();
            try {
                System.out.println("線程" + threadNum + "獲取鎖");
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            } finally {
                // finally中解鎖
                reentrantLock.unlock();
                System.out.println("線程" + threadNum +"釋放鎖");
            }
        }).start();
        Thread.sleep(999);
    }

    Thread.sleep(100000);
}

運(yùn)行結(jié)果:

線程0獲取鎖
線程0釋放鎖
線程1獲取鎖
線程1釋放鎖
線程3獲取鎖
線程3釋放鎖
線程2獲取鎖
線程2釋放鎖
線程5獲取鎖
線程5釋放鎖
線程4獲取鎖
線程4釋放鎖
....

  • 默認(rèn)構(gòu)造函數(shù)創(chuàng)建的是非公平鎖
  • 運(yùn)行結(jié)果可以看到線程3優(yōu)先于線程2獲取鎖(這個(gè)結(jié)果是人為造的,很難模擬出來)。

加鎖原理

1.構(gòu)造函數(shù)創(chuàng)建鎖對(duì)象

public ReentrantLock() {
 	sync = new NonfairSync();
}

默認(rèn)構(gòu)造函數(shù),創(chuàng)建了NonfairSync,非公平鎖同步器,它是繼承自AQS.

2.第一個(gè)線程加鎖時(shí),不存在競爭,如下圖:

// ReentrantLock.NonfairSync#lock
final void lock() {
    // 用 cas 嘗試(僅嘗試一次)將 state 從 0 改為 1, 如果成功表示【獲得了獨(dú)占鎖】
    if (compareAndSetState(0, 1))
        // 設(shè)置當(dāng)前線程為獨(dú)占線程
        setExclusiveOwnerThread(Thread.currentThread());
    else
        acquire(1);//失敗進(jìn)入
}

  • cas修改state從0到1,獲取鎖
  • 設(shè)置鎖對(duì)象的線程為當(dāng)前線程

3.第二個(gè)線程申請(qǐng)加鎖時(shí),出現(xiàn)鎖競爭,如下圖:

Thread-1 執(zhí)行,CAS 嘗試將 state 由 0 改為 1,結(jié)果失?。ǖ谝淮危?,進(jìn)入 acquire 邏輯

// AbstractQueuedSynchronizer#acquire
public final void acquire(int arg) {
    // tryAcquire 嘗試獲取鎖失敗時(shí), 會(huì)調(diào)用 addWaiter 將當(dāng)前線程封裝成node入隊(duì),acquireQueued 阻塞當(dāng)前線程,
    // acquireQueued 返回 true 表示掛起過程中線程被中斷喚醒過,false 表示未被中斷過
    if (!tryAcquire(arg) && acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
        // 如果線程被中斷了邏輯來到這,完成一次真正的打斷效果
        selfInterrupt();
}

調(diào)用tryAcquire方法嘗試獲取鎖,這里由子類NonfairSync實(shí)現(xiàn)。

如果tryAcquire獲取鎖失敗,通過addWaiter方法將當(dāng)前線程封裝成節(jié)點(diǎn),入隊(duì)

acquireQueued方法會(huì)將當(dāng)前線程阻塞

// ReentrantLock.NonfairSync#tryAcquire
protected final boolean tryAcquire(int acquires) {
    return nonfairTryAcquire(acquires);
}
// 搶占成功返回 true,搶占失敗返回 false
final boolean nonfairTryAcquire(int acquires) {
    final Thread current = Thread.currentThread();
    // state 值
    int c = getState();
    // 條件成立說明當(dāng)前處于【無鎖狀態(tài)】
    if (c == 0) {
        //如果還沒有獲得鎖,嘗試用cas獲得,這里體現(xiàn)非公平性: 不去檢查 AQS 隊(duì)列是否有阻塞線程直接獲取鎖        
    	if (compareAndSetState(0, acquires)) {
            // 獲取鎖成功設(shè)置當(dāng)前線程為獨(dú)占鎖線程。
            setExclusiveOwnerThread(current);
            return true;
         }    
	} 
    // 這部分是重入鎖的原理    
   	// 如果已經(jīng)有線程獲得了鎖, 獨(dú)占鎖線程還是當(dāng)前線程, 表示【發(fā)生了鎖重入】
	else if (current == getExclusiveOwnerThread()) {
        // 更新鎖重入的值
        int nextc = c + acquires;
        // 越界判斷,當(dāng)重入的深度很深時(shí),會(huì)導(dǎo)致 nextc < 0,int值達(dá)到最大之后再 + 1 變負(fù)數(shù)
        if (nextc < 0) // overflow
            throw new Error("Maximum lock count exceeded");
        // 更新 state 的值,這里不使用 cas 是因?yàn)楫?dāng)前線程正在持有鎖,所以這里的操作相當(dāng)于在一個(gè)管程內(nèi)
        setState(nextc);
        return true;
    }
    // 獲取失敗
    return false;
}

正是這個(gè)方法體現(xiàn)了非公平鎖,在nonfairTryAcquire如果發(fā)現(xiàn)state=0,無鎖的情況,它會(huì)忽略隊(duì)列中等待的線程,優(yōu)先獲取一次鎖,相當(dāng)于"插隊(duì)"。

4.第二個(gè)線程tryAcquire申請(qǐng)鎖失敗,通過執(zhí)行addWaiter方法加入到隊(duì)列中。

  • 圖中黃色三角表示該 Node 的 waitStatus 狀態(tài),其中 0 為默認(rèn)正常狀態(tài)
  • Node 的創(chuàng)建是懶惰的,其中第一個(gè) Node 稱為 Dummy(啞元)或哨兵,用來占位,并不關(guān)聯(lián)線程。
// AbstractQueuedSynchronizer#addWaiter,返回當(dāng)前線程的 node 節(jié)點(diǎn)
private Node addWaiter(Node mode) {
    // 將當(dāng)前線程關(guān)聯(lián)到一個(gè) Node 對(duì)象上, 模式為獨(dú)占模式   
    Node node = new Node(Thread.currentThread(), mode);
    Node pred = tail;
    // 快速入隊(duì),如果 tail 不為 null,說明存在阻塞隊(duì)列
    if (pred != null) {
        // 將當(dāng)前節(jié)點(diǎn)的前驅(qū)節(jié)點(diǎn)指向 尾節(jié)點(diǎn)
        node.prev = pred;
        // 通過 cas 將 Node 對(duì)象加入 AQS 隊(duì)列,成為尾節(jié)點(diǎn),【尾插法】
        if (compareAndSetTail(pred, node)) {
            pred.next = node;// 雙向鏈表
            return node;
        }
    }
    // 初始時(shí)隊(duì)列為空,或者 CAS 失敗進(jìn)入這里
    enq(node);
    return node;
}
// AbstractQueuedSynchronizer#enq
private Node enq(final Node node) {
    // 自旋入隊(duì),必須入隊(duì)成功才結(jié)束循環(huán)
    for (;;) {
        Node t = tail;
        // 說明當(dāng)前鎖被占用,且當(dāng)前線程可能是【第一個(gè)獲取鎖失敗】的線程,【還沒有建立隊(duì)列】
        if (t == null) {
            // 設(shè)置一個(gè)【啞元節(jié)點(diǎn)】,頭尾指針都指向該節(jié)點(diǎn)
            if (compareAndSetHead(new Node()))
                tail = head;
        } else {
            // 自旋到這,普通入隊(duì)方式,首先賦值尾節(jié)點(diǎn)的前驅(qū)節(jié)點(diǎn)【尾插法】
            node.prev = t;
            // 【在設(shè)置完尾節(jié)點(diǎn)后,才更新的原始尾節(jié)點(diǎn)的后繼節(jié)點(diǎn),所以此時(shí)從前往后遍歷會(huì)丟失尾節(jié)點(diǎn)】
            if (compareAndSetTail(t, node)) {
                //【此時(shí) t.next  = null,并且這里已經(jīng) CAS 結(jié)束,線程并不是安全的】
                t.next = node;
                return t;	// 返回當(dāng)前 node 的前驅(qū)節(jié)點(diǎn)
            }
        }
    }
}

5.第二個(gè)線程加入隊(duì)列后,現(xiàn)在要做的是想辦法阻塞線程,不讓它執(zhí)行,就看acquireQueued的了。

  • 圖中黃色三角表示該 Node 的 waitStatus 狀態(tài),0 為默認(rèn)正常狀態(tài), 但是-1狀態(tài)表示它肩負(fù)喚醒下一個(gè)節(jié)點(diǎn)的線程。
  • 灰色表示線程阻塞了。
inal boolean acquireQueued(final Node node, int arg) {
    // true 表示當(dāng)前線程搶占鎖失敗,false 表示成功
    boolean failed = true;
    try {
        // 中斷標(biāo)記,表示當(dāng)前線程是否被中斷
        boolean interrupted = false;
        for (;;) {
            // 獲得當(dāng)前線程節(jié)點(diǎn)的前驅(qū)節(jié)點(diǎn)
            final Node p = node.predecessor();
            // 前驅(qū)節(jié)點(diǎn)是 head, FIFO 隊(duì)列的特性表示輪到當(dāng)前線程可以去獲取鎖
            if (p == head && tryAcquire(arg)) {
                // 獲取成功, 設(shè)置當(dāng)前線程自己的 node 為 head
                setHead(node);
                p.next = null; // help GC
                // 表示搶占鎖成功
                failed = false;
                // 返回當(dāng)前線程是否被中斷
                return interrupted;
            }
            // 判斷是否應(yīng)當(dāng) park,返回 false 后需要新一輪的循環(huán),返回 true 進(jìn)入條件二阻塞線程
            if (shouldParkAfterFailedAcquire(p, node) && parkAndCheckInterrupt())
                // 條件二返回結(jié)果是當(dāng)前線程是否被打斷,沒有被打斷返回 false 不進(jìn)入這里的邏輯
                // 【就算被打斷了,也會(huì)繼續(xù)循環(huán),并不會(huì)返回】
                interrupted = true;
        }
    } finally {
        // 【可打斷模式下才會(huì)進(jìn)入該邏輯】
        if (failed)
            cancelAcquire(node);
    }
}
  • acquireQueued 會(huì)在一個(gè)自旋中不斷嘗試獲得鎖,失敗后進(jìn)入 park 阻塞
  • 如果當(dāng)前線程是在 head 節(jié)點(diǎn)后,也就是第一個(gè)節(jié)點(diǎn),又會(huì)直接多一次機(jī)會(huì) tryAcquire 嘗試獲取鎖,如果還是被占用,會(huì)返回失敗。
private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) {
    int ws = pred.waitStatus;
    // 表示前置節(jié)點(diǎn)是個(gè)可以喚醒當(dāng)前節(jié)點(diǎn)的節(jié)點(diǎn),返回 true
    if (ws == Node.SIGNAL)
        return true;
    // 前置節(jié)點(diǎn)的狀態(tài)處于取消狀態(tài),需要【刪除前面所有取消的節(jié)點(diǎn)】, 返回到外層循環(huán)重試
    if (ws > 0) {
        do {
            node.prev = pred = pred.prev;
        } while (pred.waitStatus > 0);
        // 獲取到非取消的節(jié)點(diǎn),連接上當(dāng)前節(jié)點(diǎn)
        pred.next = node;
    // 默認(rèn)情況下 node 的 waitStatus 是 0,進(jìn)入這里的邏輯
    } else {
        // 【設(shè)置上一個(gè)節(jié)點(diǎn)狀態(tài)為 Node.SIGNAL】,返回外層循環(huán)重試
        compareAndSetWaitStatus(pred, ws, Node.SIGNAL);
    }
    // 返回不應(yīng)該 park,再次嘗試一次
    return false;
}
  • shouldParkAfterFailedAcquire發(fā)現(xiàn)前驅(qū)節(jié)點(diǎn)等待狀態(tài)是-1, 返回true,表示需要阻塞。
  • shouldParkAfterFailedAcquire發(fā)現(xiàn)前驅(qū)節(jié)點(diǎn)等待狀態(tài)大于0,說明是無效節(jié)點(diǎn),會(huì)進(jìn)行清理。
  • shouldParkAfterFailedAcquire發(fā)現(xiàn)前驅(qū)節(jié)點(diǎn)等待狀態(tài)等于0,將前驅(qū) node 的 waitStatus 改為 -1,返回 false。
private final boolean parkAndCheckInterrupt() {
    // 阻塞當(dāng)前線程,如果打斷標(biāo)記已經(jīng)是 true, 則 park 會(huì)失效
    LockSupport.park(this);
    // 判斷當(dāng)前線程是否被打斷,清除打斷標(biāo)記
    return Thread.interrupted();
}
  • 通過不斷自旋嘗試獲取鎖,最終前驅(qū)節(jié)點(diǎn)的等待狀態(tài)為-1的時(shí)候,進(jìn)行阻塞當(dāng)前線程。
  • 通過調(diào)用LockSupport.park方法進(jìn)行阻塞。

6.多個(gè)線程嘗試獲取鎖,競爭失敗后,最終形成下面的圖形。

釋放鎖原理

1.第一個(gè)線程通過調(diào)用unlock方法釋放鎖。

public void unlock() {
    sync.release(1);
}

最終調(diào)用的是同步器的release方法。

設(shè)置鎖定的線程exclusiveOwnerThread為null

設(shè)置鎖的state為0

// AbstractQueuedSynchronizer#release
public final boolean release(int arg) {
    // 嘗試釋放鎖,tryRelease 返回 true 表示當(dāng)前線程已經(jīng)【完全釋放鎖,重入的釋放了】
    if (tryRelease(arg)) {
        // 隊(duì)列頭節(jié)點(diǎn)
        Node h = head;
        // 頭節(jié)點(diǎn)什么時(shí)候是空?沒有發(fā)生鎖競爭,沒有競爭線程創(chuàng)建啞元節(jié)點(diǎn)
        // 條件成立說明阻塞隊(duì)列有等待線程,需要喚醒 head 節(jié)點(diǎn)后面的線程
        if (h != null && h.waitStatus != 0)
            unparkSuccessor(h);
        return true;
    }    
    return false;
}

進(jìn)入 tryRelease,設(shè)置 exclusiveOwnerThread 為 null,state = 0

當(dāng)前隊(duì)列不為 null,并且 head 的 waitStatus = -1,進(jìn)入 unparkSuccessor, 喚醒阻塞的線程

2.線程一通過調(diào)用tryRelease方法釋放鎖,該類的實(shí)現(xiàn)是在子類中

// ReentrantLock.Sync#tryRelease
protected final boolean tryRelease(int releases) {
    // 減去釋放的值,可能重入
    int c = getState() - releases;
    // 如果當(dāng)前線程不是持有鎖的線程直接報(bào)錯(cuò)
    if (Thread.currentThread() != getExclusiveOwnerThread())
        throw new IllegalMonitorStateException();
    // 是否已經(jīng)完全釋放鎖
    boolean free = false;
    // 支持鎖重入, 只有 state 減為 0, 才完全釋放鎖成功
    if (c == 0) {
        free = true;
        setExclusiveOwnerThread(null);
    }
    // 當(dāng)前線程就是持有鎖線程,所以可以直接更新鎖,不需要使用 CAS
    setState(c);
    return free;
}

修改鎖資源的state

3.喚醒隊(duì)列中第一個(gè)線程Thread1

private void unparkSuccessor(Node node) {
    // 當(dāng)前節(jié)點(diǎn)的狀態(tài)
    int ws = node.waitStatus;    
    if (ws < 0)        
        // 【嘗試重置狀態(tài)為 0】,因?yàn)楫?dāng)前節(jié)點(diǎn)要完成對(duì)后續(xù)節(jié)點(diǎn)的喚醒任務(wù)了,不需要 -1 了
        compareAndSetWaitStatus(node, ws, 0);    
    // 找到需要 unpark 的節(jié)點(diǎn),當(dāng)前節(jié)點(diǎn)的下一個(gè)    
    Node s = node.next;    
    // 已取消的節(jié)點(diǎn)不能喚醒,需要找到距離頭節(jié)點(diǎn)最近的非取消的節(jié)點(diǎn)
    if (s == null || s.waitStatus > 0) {
        s = null;
        // AQS 隊(duì)列【從后至前】找需要 unpark 的節(jié)點(diǎn),直到 t == 當(dāng)前的 node 為止,找不到就不喚醒了
        for (Node t = tail; t != null && t != node; t = t.prev)
            // 說明當(dāng)前線程狀態(tài)需要被喚醒
            if (t.waitStatus <= 0)
                // 置換引用
                s = t;
    }
    // 【找到合適的可以被喚醒的 node,則喚醒線程】
    if (s != null)
        LockSupport.unpark(s.thread);
}
  • 從后往前找到隊(duì)列中距離 head 最近的一個(gè)沒取消的 Node,unpark 恢復(fù)其運(yùn)行,本例中即為 Thread-1
  • thread1活了,開始重新去獲取鎖,也就是前面acquireQueued中的流程。

為什么這里查找喚醒的節(jié)點(diǎn)是從后往前,而不是從前往后呢?

從后向前的喚醒的原因:enq 方法中,節(jié)點(diǎn)是尾插法,首先賦值的是尾節(jié)點(diǎn)的前驅(qū)節(jié)點(diǎn),此時(shí)前驅(qū)節(jié)點(diǎn)的 next 并沒有指向尾節(jié)點(diǎn),從前遍歷會(huì)丟失尾節(jié)點(diǎn)。

4.Thread1恢復(fù)執(zhí)行流程

  • 喚醒的Thread-1 線程會(huì)從 park 位置開始執(zhí)行,如果加鎖成功(沒有競爭),設(shè)置了exclusiveOwnerThread為Thread-1, state=1。
  • head 指向剛剛 Thread-1 所在的 Node,該 Node 會(huì)清空 Thread
  • 原本的 head 因?yàn)閺逆湵頂嚅_,而可被垃圾回收

5.另一種可能,突然來了Thread-4來競爭,體現(xiàn)非公平鎖

如果這時(shí)有其它線程來競爭鎖,例如這時(shí)有 Thread-4 來了并搶占了鎖,很有可能搶占成功。

Thread-4 被設(shè)置為 exclusiveOwnerThread,state = 1

Thread-1 再次進(jìn)入 acquireQueued 流程,獲取鎖失敗,重新進(jìn)入 park 阻塞

公平鎖實(shí)現(xiàn)

演示

@Test
public void testfairLock() throws InterruptedException {
    // 有參構(gòu)造函數(shù),true表示公平鎖,false表示非公平鎖
    ReentrantLock reentrantLock = new ReentrantLock(true);

    for (int i = 0; i < 10; i++) {
        final int threadNum = i;
        new Thread(() -> {
            reentrantLock.lock();
            try {
                System.out.println("線程" + threadNum + "獲取鎖");
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            } finally {
                // finally中解鎖
                reentrantLock.unlock();
                System.out.println("線程" + threadNum +"釋放鎖");
            }
        }).start();
        Thread.sleep(10);
    }

    Thread.sleep(100000);
}

運(yùn)行結(jié)果:

線程0獲取鎖
線程0釋放鎖
線程1獲取鎖
線程1釋放鎖
線程2獲取鎖
線程2釋放鎖
線程3獲取鎖
線程3釋放鎖
線程4獲取鎖
線程4釋放鎖
線程5獲取鎖
線程5釋放鎖
線程6獲取鎖
線程6釋放鎖
線程7獲取鎖
線程7釋放鎖
線程8獲取鎖
線程8釋放鎖
線程9獲取鎖
線程9釋放鎖

ReentrantLock有參構(gòu)造函數(shù),true表示公平鎖,false表示非公平鎖

觀察運(yùn)行結(jié)果,所有獲取鎖的過程都是根據(jù)申請(qǐng)鎖的時(shí)間保持一致。

原理實(shí)現(xiàn)

公平鎖和非公鎖的整體流程基本是一致的,唯一不同的是嘗試獲取鎖tryAcquire的實(shí)現(xiàn)。

static final class FairSync extends Sync {
    private static final long serialVersionUID = -3000897897090466540L;
    final void lock() {
        acquire(1);
    }

    protected final boolean tryAcquire(int acquires) {
        final Thread current = Thread.currentThread();
        int c = getState();
        if (c == 0) {
            // 先檢查 AQS 隊(duì)列中是否有前驅(qū)節(jié)點(diǎn), 沒有(false)才去競爭
            if (!hasQueuedPredecessors() &&
                compareAndSetState(0, acquires)) {
                setExclusiveOwnerThread(current);
                return true;
            }
        }
        // 鎖重入
        return false;
    }
}
public final boolean hasQueuedPredecessors() {    
    Node t = tail;
    Node h = head;
    Node s;    
    // 頭尾指向一個(gè)節(jié)點(diǎn),鏈表為空,返回false
    return h != t &&
        // 頭尾之間有節(jié)點(diǎn),判斷頭節(jié)點(diǎn)的下一個(gè)是不是空
        // 不是空進(jìn)入最后的判斷,第二個(gè)節(jié)點(diǎn)的線程是否是本線程,不是返回 true,表示當(dāng)前節(jié)點(diǎn)有前驅(qū)節(jié)點(diǎn)
        ((s = h.next) == null || s.thread != Thread.currentThread());
}

與非公平鎖最大的區(qū)別是:公平鎖獲取鎖的時(shí)候先檢查 AQS 隊(duì)列中是否有非當(dāng)前線程的等待節(jié)點(diǎn),沒有才去 CAS 競爭,有的話,就老老實(shí)實(shí)排隊(duì)去吧。而非公平鎖會(huì)嘗試搶一次鎖,如果搶不到的話,老老實(shí)實(shí)排隊(duì)去吧。

總結(jié)

非公平鎖和公平鎖的兩處不同:

  • 非公平鎖在調(diào)用 lock 后,首先就會(huì)調(diào)用 CAS 進(jìn)行一次搶鎖,如果這個(gè)時(shí)候恰巧鎖沒有被占用,那么直接就獲取到鎖返回了。
  • 非公平鎖在 CAS 失敗后,和公平鎖一樣都會(huì)進(jìn)入到 tryAcquire 方法,在 tryAcquire 方法中,如果發(fā)現(xiàn)鎖這個(gè)時(shí)候被釋放了(state == 0),非公平鎖會(huì)直接 CAS 搶鎖,但是公平鎖會(huì)判斷等待隊(duì)列是否有線程處于等待狀態(tài),如果有則不去搶鎖,乖乖排到后面。

公平鎖和非公平鎖就這兩點(diǎn)區(qū)別,如果這兩次 CAS 都不成功,那么后面非公平鎖和公平鎖是一樣的,都要進(jìn)入到阻塞隊(duì)列等待喚醒。

相對(duì)來說,非公平鎖會(huì)有更好的性能,因?yàn)樗耐掏铝勘容^大。當(dāng)然,非公平鎖讓獲取鎖的時(shí)間變得更加不確定,可能會(huì)導(dǎo)致在阻塞隊(duì)列中的線程長期處于饑餓狀態(tài)。

以上就是圖解Java ReentrantLock公平鎖和非公平鎖的實(shí)現(xiàn)的詳細(xì)內(nèi)容,更多關(guān)于Java ReentrantLock公平鎖 非公平鎖的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!

相關(guān)文章

  • 解析HashMap中的put方法執(zhí)行流程

    解析HashMap中的put方法執(zhí)行流程

    在Java集合中,HashMap的重要性不言而喻,作為一種存儲(chǔ)鍵值對(duì)的數(shù)據(jù)結(jié)構(gòu),它在日常開發(fā)中有著非常多的應(yīng)用場景,也是面試中的高頻考點(diǎn),本篇文章就來分析一下HashMap集合中的put方法
    2021-12-12
  • 詳解解決IDEA2020.1版本的lombok插件問題

    詳解解決IDEA2020.1版本的lombok插件問題

    這篇文章主要介紹了詳解解決IDEA2020.1版本的lombok插件問題。文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧
    2020-11-11
  • 詳解Java日志正確使用姿勢

    詳解Java日志正確使用姿勢

    這篇文章主要介紹了Java日志正確使用姿勢,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧
    2019-04-04
  • 詳解Java阻塞隊(duì)列(BlockingQueue)的實(shí)現(xiàn)原理

    詳解Java阻塞隊(duì)列(BlockingQueue)的實(shí)現(xiàn)原理

    這篇文章主要介紹了詳解Java阻塞隊(duì)列(BlockingQueue)的實(shí)現(xiàn)原理,阻塞隊(duì)列是Java util.concurrent包下重要的數(shù)據(jù)結(jié)構(gòu),有興趣的可以了解一下
    2017-06-06
  • Java ArrayList類的基礎(chǔ)使用講解

    Java ArrayList類的基礎(chǔ)使用講解

    數(shù)組的長度是固定的,無法適應(yīng)數(shù)據(jù)變化的需求。為了解決這個(gè)問題,Java提供了另一個(gè)容器 java.util.ArrayList集合類,讓我們可以更便捷的存儲(chǔ)和操作對(duì)象數(shù)據(jù)。本文就將通過示例聊聊ArrayList類的基礎(chǔ)使用,感興趣的可以了解一下
    2022-10-10
  • 在eclipse中修改tomcat的部署路徑操作

    在eclipse中修改tomcat的部署路徑操作

    這篇文章主要介紹了在eclipse中修改tomcat的部署路徑操作,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過來看看吧
    2021-01-01
  • Fluent Mybatis快速入門詳細(xì)教程

    Fluent Mybatis快速入門詳細(xì)教程

    由于FluentMybatis是基于mybatis上做封裝和擴(kuò)展的,所以這里主要聊聊mybatis處理的方式,以及給出FluentMybatis的解放方案。對(duì)Fluent Mybatis入門相關(guān)知識(shí)感興趣的朋友一起看看吧
    2021-08-08
  • java正則表達(dá)式處理花括號(hào)內(nèi)容替換賦值問題

    java正則表達(dá)式處理花括號(hào)內(nèi)容替換賦值問題

    這篇文章主要介紹了java正則表達(dá)式處理花括號(hào)內(nèi)容替換賦值問題,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教
    2023-05-05
  • Spring接口ApplicationRunner用法詳解

    Spring接口ApplicationRunner用法詳解

    這篇文章主要介紹了Spring接口ApplicationRunner的作用和使用介紹,本文通過示例代碼給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下
    2023-08-08
  • JavaWeb實(shí)現(xiàn)表單提交的示例詳解

    JavaWeb實(shí)現(xiàn)表單提交的示例詳解

    這篇文章主要介紹了如何利用JavaWeb實(shí)現(xiàn)表單提交功能,文中的示例代碼講解詳細(xì),對(duì)我們學(xué)習(xí)JavaWeb有一定幫助,感興趣的可以了解一下
    2022-03-03

最新評(píng)論