溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊(cè)×
其他方式登錄
點(diǎn)擊 登錄注冊(cè) 即表示同意《億速云用戶服務(wù)條款》

怎么在Java中使用ReentrantLock實(shí)現(xiàn)并發(fā)編程

發(fā)布時(shí)間:2021-05-26 09:24:59 來(lái)源:億速云 閱讀:149 作者:Leah 欄目:開(kāi)發(fā)技術(shù)

這篇文章給大家介紹怎么在Java中使用ReentrantLock實(shí)現(xiàn)并發(fā)編程,內(nèi)容非常詳細(xì),感興趣的小伙伴們可以參考借鑒,希望對(duì)大家能有所幫助。

一、首先看圖

怎么在Java中使用ReentrantLock實(shí)現(xiàn)并發(fā)編程

二、lock()跟蹤源碼

怎么在Java中使用ReentrantLock實(shí)現(xiàn)并發(fā)編程

這里對(duì)公平鎖和非公平鎖做了不同實(shí)現(xiàn),由構(gòu)造方法參數(shù)決定是否公平。

public ReentrantLock(boolean fair) {
     sync = fair ? new FairSync() : new NonfairSync();
}

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

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);
    }

    protected final boolean tryAcquire(int acquires) {
        return nonfairTryAcquire(acquires);
    }
}

代碼量很少。首先compareAndSetState(0, 1)通過(guò)CAS(期望值0,新值1,內(nèi)存值stateOffset)

  • 如果修改成功,即搶占到鎖,setExclusiveOwnerThread(Thread.currentThread());將AQS中的變量exclusiveOwnerThread設(shè)置為當(dāng)前搶占到鎖的線程,也就是圖中的ThreadA。

  • 若沒(méi)有搶占成功,證明此時(shí)鎖被占用,執(zhí)行方法acquire(1);。

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

這里主要看兩個(gè)方法tryAcquire(arg)acquireQueued(addWaiter(Node.EXCLUSIVE), arg)。當(dāng)滿足if條件后,會(huì)給當(dāng)前線程標(biāo)記一個(gè)interrupt狀態(tài)。

2.1.1 tryAcquire(arg)

這個(gè)方法又有多個(gè)實(shí)現(xiàn)。這里看NonfairSync非公平鎖。

怎么在Java中使用ReentrantLock實(shí)現(xiàn)并發(fā)編程

protected final boolean tryAcquire(int acquires) {
    return nonfairTryAcquire(acquires);
}

final boolean nonfairTryAcquire(int acquires) {
     final Thread current = Thread.currentThread();
     int c = getState();
     if (c == 0) {
         if (compareAndSetState(0, acquires)) {
             setExclusiveOwnerThread(current);
             return true;
         }
     }
     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;
 }

在這個(gè)方法中,還不死心,首先會(huì)判斷下AQS中的state是否為0,為0也就是說(shuō)距離上次嘗試獲取鎖到現(xiàn)在準(zhǔn)備進(jìn)入隊(duì)列(雙向鏈表)中這段時(shí)間內(nèi),鎖已經(jīng)被釋放,可以重新CAS嘗試獲取鎖。

如果當(dāng)前鎖還是被持有狀態(tài),就是state!=0,就會(huì)判斷,當(dāng)前線程是不是當(dāng)前持有鎖的線程exclusiveOwnerThread,如果是,則state+1,從這里可以看出state表示的是重入次數(shù)。

全部不滿足,返回false。

2.1.2 acquireQueued(addWaiter(Node.EXCLUSIVE), arg)

addWaiter

private Node addWaiter(Node mode) {
    Node node = new Node(Thread.currentThread(), mode);
    // Try the fast path of enq; backup to full enq on failure
    Node pred = tail;
    if (pred != null) {
        node.prev = pred;
        if (compareAndSetTail(pred, node)) {
            pred.next = node;
            return node;
        }
    }
    enq(node);
    return node;
}

tryAcquire(arg)返回false,證明當(dāng)前線程還是沒(méi)有獲取到鎖。那么就要進(jìn)入隊(duì)列等待了,首先addWaiter方法,將當(dāng)前線程封裝成一個(gè)Node,如果pred不為空,則將當(dāng)前節(jié)點(diǎn)做鏈表的尾部插入,同時(shí)為了防止在此期間前序節(jié)點(diǎn)已經(jīng)不在隊(duì)列中了,也會(huì)運(yùn)用CAS操作來(lái)執(zhí)行(期望值pred,新值node,內(nèi)存值tailOffset)。

如果前序節(jié)點(diǎn)為空,或者在CAS時(shí)發(fā)現(xiàn)前序節(jié)點(diǎn)已經(jīng)不存在了,則重新構(gòu)建鏈表,將當(dāng)前節(jié)點(diǎn)封裝的Node,加入到鏈表當(dāng)中。

private Node enq(final Node node) {
    for (;;) {
        Node t = tail;
        if (t == null) { // Must initialize
            if (compareAndSetHead(new Node()))
                tail = head;
        } else {
            node.prev = t;
            if (compareAndSetTail(t, node)) {
                t.next = node;
                return t;
            }
        }
    }
}

加入完成后,返回當(dāng)前node節(jié)點(diǎn),進(jìn)入acquireQueued方法。

acquireQueued

final boolean acquireQueued(final Node node, int arg) {
    boolean failed = true;
    try {
        boolean interrupted = false;
        for (;;) {
        	//獲取到當(dāng)前node節(jié)點(diǎn)的上一個(gè)節(jié)點(diǎn)
            final Node p = node.predecessor();
            //如果當(dāng)前的上個(gè)節(jié)點(diǎn)就是頭節(jié)點(diǎn),會(huì)再次嘗試獲取鎖
            if (p == head && tryAcquire(arg)) {
            	//獲取成功,將當(dāng)前節(jié)點(diǎn)置空,并成為新的頭節(jié)點(diǎn)
                setHead(node);
				//這個(gè)p已經(jīng)沒(méi)用了,防止內(nèi)存泄漏,直接指向null,下次GC時(shí)回收
                p.next = null; // help GC
                //不需要取消
                failed = false;
                //return false,不需要中斷當(dāng)前線程
                return interrupted;
            }
            if (shouldParkAfterFailedAcquire(p, node) &&
                parkAndCheckInterrupt())
                interrupted = true;
        }
    } finally {
        if (failed)
            cancelAcquire(node);
    }
}

這里是一個(gè)自旋操作,首先拿到當(dāng)前線程封裝節(jié)點(diǎn)的上一個(gè)節(jié)點(diǎn),如果滿足第一個(gè)if條件if (p == head && tryAcquire(arg)),證明上個(gè)節(jié)點(diǎn)為頭節(jié)點(diǎn),則此時(shí)當(dāng)前線程也會(huì)再次嘗試獲取鎖,獲取鎖成功,證明此時(shí)沒(méi)有別的線程在隊(duì)列中了,則將當(dāng)前node清空并設(shè)置為頭節(jié)點(diǎn),返回不需要中斷當(dāng)前線程。

在第二個(gè)if條件中if (shouldParkAfterFailedAcquire(p, node) && parkAndCheckInterrupt())。走到這里證明當(dāng)前線程不是第一個(gè)線程節(jié)點(diǎn),或者沒(méi)有搶占到鎖,shouldParkAfterFailedAcquire這個(gè)方法見(jiàn)名知意,在搶占失敗后是否需要park阻塞,里面主要是用于清理雙向鏈表中被取消的節(jié)點(diǎn)線程和未被阻塞的節(jié)點(diǎn)線程。

private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) {
    int ws = pred.waitStatus;//獲取前置節(jié)點(diǎn)的等待狀態(tài)
    if (ws == Node.SIGNAL)
		//前置節(jié)點(diǎn)的等待狀態(tài)為-1,表示前置節(jié)點(diǎn)在隊(duì)列中阻塞,那么當(dāng)前節(jié)點(diǎn)也需要被阻塞在隊(duì)列中
        return true;
    if (ws > 0) {
		//前置節(jié)點(diǎn)等待狀態(tài)大于0,此前置節(jié)點(diǎn)已經(jīng)被取消,循環(huán)遍歷清除所有已被取消的節(jié)點(diǎn)。
        do {
            node.prev = pred = pred.prev;
        } while (pred.waitStatus > 0);
        pred.next = node;
    } else {
		//前置節(jié)點(diǎn)等待狀態(tài)小于等于0,且不等于-1,也就是沒(méi)有被阻塞也沒(méi)有被取消
		//則將前置節(jié)點(diǎn)設(shè)置為阻塞狀態(tài)。
        compareAndSetWaitStatus(pred, ws, Node.SIGNAL);
    }
    return false;
}
  • 前置節(jié)點(diǎn)的等待狀態(tài)為-1,表示前置節(jié)點(diǎn)在隊(duì)列中阻塞,那么當(dāng)前節(jié)點(diǎn)也需要被阻塞在隊(duì)列中

  • 前置節(jié)點(diǎn)等待狀態(tài)大于0,此前置節(jié)點(diǎn)已經(jīng)被取消,循環(huán)遍歷清除所有已被取消的節(jié)點(diǎn)。

  • 前置節(jié)點(diǎn)等待狀態(tài)小于等于0,且不等于-1,也就是沒(méi)有被阻塞也沒(méi)有被取消。則將前置節(jié)點(diǎn)設(shè)置為阻塞狀態(tài)。

到這里,基于非公平鎖的實(shí)現(xiàn)結(jié)束。

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

公平鎖和樂(lè)觀鎖的區(qū)別就在于,非公平鎖acquire(1)前會(huì)先嘗試獲取鎖,公平鎖直接acquire(1)。

static final class FairSync extends Sync {
        private static final long serialVersionUID = -3000897897090466540L;

        final void lock() {
            acquire(1);
        }
}
2.2.1 tryAcquire(arg)

在tryAcquire中也和非公平鎖有一定的區(qū)別。在當(dāng)前鎖沒(méi)有被占有時(shí)。非公平鎖不用考慮目前AQS隊(duì)列中的排隊(duì)情況,直接通過(guò)CAS嘗試獲取鎖。公平鎖會(huì)看目前隊(duì)列的狀態(tài),再來(lái)決定是嘗試占有鎖還是在隊(duì)列中等待。

protected final boolean tryAcquire(int acquires) {
   final Thread current = Thread.currentThread();
   int c = getState();
   if (c == 0) {
       if (!hasQueuedPredecessors() &&
           compareAndSetState(0, acquires)) {
           setExclusiveOwnerThread(current);
           return true;
       }
   }
   else if (current == getExclusiveOwnerThread()) {
       int nextc = c + acquires;
       if (nextc < 0)
           throw new Error("Maximum lock count exceeded");
       setState(nextc);
       return true;
   }
   return false;
}

java基本數(shù)據(jù)類型有哪些

Java的基本數(shù)據(jù)類型分為:1、整數(shù)類型,用來(lái)表示整數(shù)的數(shù)據(jù)類型。2、浮點(diǎn)類型,用來(lái)表示小數(shù)的數(shù)據(jù)類型。3、字符類型,字符類型的關(guān)鍵字是“char”。4、布爾類型,是表示邏輯值的基本數(shù)據(jù)類型。

關(guān)于怎么在Java中使用ReentrantLock實(shí)現(xiàn)并發(fā)編程就分享到這里了,希望以上內(nèi)容可以對(duì)大家有一定的幫助,可以學(xué)到更多知識(shí)。如果覺(jué)得文章不錯(cuò),可以把它分享出去讓更多的人看到。

向AI問(wèn)一下細(xì)節(jié)

免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點(diǎn)不代表本網(wǎng)站立場(chǎng),如果涉及侵權(quán)請(qǐng)聯(lián)系站長(zhǎng)郵箱:is@yisu.com進(jìn)行舉報(bào),并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。

AI