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

Java源碼解析之詳解ReentrantLock

 更新時(shí)間:2021年06月29日 08:59:00   作者:北洛  
今天給大家?guī)?lái)的是關(guān)于Java并發(fā)的相關(guān)知識(shí),文章圍繞著ReentrantLock源碼展開,文中有非常詳細(xì)的介紹及代碼示例,需要的朋友可以參考下

ReentrantLock

ReentrantLock是一種可重入的互斥鎖,它的行為和作用與關(guān)鍵字synchronized有些類似,在并發(fā)場(chǎng)景下可以讓多個(gè)線程按照一定的順序訪問同一資源。相比synchronized,ReentrantLock多了可擴(kuò)展的能力,比如我們可以創(chuàng)建一個(gè)名為MyReentrantLock的類繼承ReentrantLock,并重寫部分方法使其更加高效。

當(dāng)一個(gè)線程調(diào)用ReentrantLock.lock()方法時(shí),如果ReentrantLock沒有被其他線程持有,且不存在額外的線程與當(dāng)前線程競(jìng)爭(zhēng)ReentrantLock,調(diào)用ReentrantLock.lock()方法后當(dāng)前線程會(huì)占有此鎖并立即返回,ReentrantLock內(nèi)部會(huì)維護(hù)當(dāng)前線程對(duì)鎖的引用計(jì)數(shù),當(dāng)線程獲取鎖時(shí)會(huì)增加其線程對(duì)鎖的引用計(jì)數(shù),當(dāng)線程釋放鎖時(shí)會(huì)減少線程對(duì)鎖的引用計(jì)數(shù),當(dāng)前線程如果在占有鎖之后,又重復(fù)獲取鎖,則會(huì)增加鎖的引用計(jì)數(shù),當(dāng)鎖的引用計(jì)數(shù)為0的時(shí)候,代表當(dāng)前線程完全釋放鎖。需要注意的是,只有占有鎖的線程才會(huì)增加鎖的引用計(jì)數(shù),當(dāng)鎖被占據(jù)時(shí),如果有其他線程要競(jìng)爭(zhēng)鎖,ReentrantLock會(huì)把其他線程加入一個(gè)競(jìng)爭(zhēng)鎖的隊(duì)列,并讓線程陷入阻塞,直到占據(jù)鎖的線程釋放了鎖,ReentrantLock才會(huì)喚醒隊(duì)列中的線程重新競(jìng)爭(zhēng)鎖。

我們用下面的例子來(lái)加深對(duì)于鎖的理解,假設(shè)我們的進(jìn)程內(nèi)目前沒有任何線程競(jìng)爭(zhēng)lock,此時(shí)鎖的引用計(jì)數(shù)為0,有一個(gè)線程Thread-1調(diào)用完下面<1>處的lock()方法成功占有鎖,此時(shí)鎖的引用計(jì)數(shù)由0變?yōu)?。之后Thread-1調(diào)用了<2>處的methodB()方法,methodB()的<4>處又獲取了一次鎖,由于lock已經(jīng)被Thread-1占據(jù),所以這里簡(jiǎn)單的對(duì)鎖的引用計(jì)數(shù)+1即可,此時(shí)鎖的引用計(jì)數(shù)為2,Thread-1執(zhí)行完methodB()的方法體后,執(zhí)行<5>處的unlock()方法釋放鎖,這里對(duì)鎖的引用計(jì)數(shù)-1,由2變?yōu)?。在調(diào)用完methodB后,執(zhí)行methodA的方法體,最后執(zhí)行<3>處的unlock()方法,將鎖的引用計(jì)數(shù)由1變?yōu)?,Thread-1完全釋放鎖。此時(shí),鎖變?yōu)闊o(wú)主狀態(tài)。

private final ReentrantLock lock = new ReentrantLock();
 
public void methodA() {
    try {
        lock.lock();//<1>
        methodB();//<2>
        //methodA body...
    } finally {
        lock.unlock();//<3>
    }
 
}
 
public void methodB() {
    try {
        lock.lock();//<4>
        //methodB body...
    } finally {
        lock.unlock();//<5>
    }
}

ReentrantLock提供了isHeldByCurrentThread()和getHoldCount()兩個(gè)方法,前者用于判斷鎖是否被當(dāng)先調(diào)用線程持有,如果被當(dāng)前調(diào)用線程持有則返回true;后者不僅會(huì)判斷鎖是否被當(dāng)前線程持有,還會(huì)返回鎖相對(duì)于當(dāng)前線程的引用計(jì)數(shù),畢竟鎖是可重入的,如果鎖沒有被任何線程持有,或者被不是持有鎖的線程調(diào)用getHoldCount()方法,就會(huì)返回0。

這兩個(gè)方法的實(shí)現(xiàn)原理也很簡(jiǎn)單,我們知道在Java中可以調(diào)用Thread.currentThread()來(lái)獲取當(dāng)前線程對(duì)象。當(dāng)我們調(diào)用ReentrantLock.lock()方法成功獲取鎖之后,ReentrantLock內(nèi)部會(huì)用一個(gè)獨(dú)占線程(exclusiveOwnerThread)字段來(lái)標(biāo)識(shí)當(dāng)前占用鎖的Thread線程對(duì)象,如果線程釋放了鎖且鎖的引用計(jì)數(shù)為0,則將獨(dú)占線程字段標(biāo)記為null。當(dāng)要判斷鎖是否被當(dāng)前線程持有,或者鎖相對(duì)于當(dāng)前線程的引用計(jì)數(shù),則獲取調(diào)用方線程的Thread對(duì)象,和內(nèi)部的獨(dú)占線程字段做下對(duì)比,如果兩者的引用相等,代表當(dāng)前線程占用了鎖,如果引用不相等,則表示當(dāng)前所可能處于無(wú)主狀態(tài),或者鎖被其他線程持有。

如下面的代碼,我們希望只有持有l(wèi)ock的線程才可以執(zhí)行methodB()和methodC()方法,就可以用isHeldByCurrentThread()和getHoldCount()進(jìn)行判斷。

private final ReentrantLock lock = new ReentrantLock();
 
public void methodA() {
    try {
        lock.lock();
        methodB();
        methodC();
        //methodA body...
    } finally {
        lock.unlock();
    }
}
 
public void methodB() {
    if (lock.getHoldCount() != 0) {
        //methodB body...
    }
}
 
public void methodC() {
    if (lock.isHeldByCurrentThread()) {
        //methodC body...
    }
}

需要注意的一點(diǎn)是,官方有給出isHeldByCurrentThread()和getHoldCount()兩個(gè)方法的使用范圍,僅針對(duì)于debug和測(cè)試。真正的生產(chǎn)環(huán)境如果有重入鎖的需要,官方還是推薦用try{}finally{}這一套,在try代碼塊里獲取鎖,在finally塊中釋放鎖。

創(chuàng)建ReentrantLock對(duì)象時(shí),如果使用的是無(wú)參構(gòu)造方法,則默認(rèn)創(chuàng)建非公平鎖(NonfairSync),如果調(diào)用的是ReentrantLock(boolean fair)有參構(gòu)造方法,fair為true則創(chuàng)建公平鎖(FairSync)。

public class ReentrantLock implements Lock, java.io.Serializable {
    //...
    //默認(rèn)創(chuàng)建非公平鎖
    public ReentrantLock() {
        sync = new NonfairSync();
    }
     
    //根據(jù)參數(shù)指定創(chuàng)建公平鎖或非公平鎖,true為公平鎖。
    public ReentrantLock(boolean fair) {
        sync = fair ? new FairSync() : new NonfairSync();
    }
    //...
}

之前說(shuō)過(guò),當(dāng)有多個(gè)線程競(jìng)爭(zhēng)鎖時(shí),獲取鎖失敗的線程,會(huì)形成一個(gè)隊(duì)列。如果有多個(gè)線程競(jìng)爭(zhēng)公平鎖時(shí),會(huì)優(yōu)先把鎖分配給等待鎖時(shí)間最長(zhǎng)的線程,即隊(duì)頭的線程,隊(duì)列中越往后的線程等待鎖的時(shí)間越短,排在隊(duì)尾的線程等待時(shí)間最短。如果使用的是非公平鎖,則不保證會(huì)按照等待時(shí)長(zhǎng)順序?qū)㈡i分配。在多線程的場(chǎng)景下,公平鎖在吞吐量方面的表現(xiàn)不如非公平鎖,但兩者在獲得鎖和保證不饑餓的差異并不大。

需要注意的是,公平鎖不能保證線程調(diào)度的公平性,競(jìng)爭(zhēng)公平鎖的多個(gè)線程中,可能會(huì)出現(xiàn)一個(gè)線程連續(xù)多次獲得鎖的情況。比如:Thread-1、Thread-2都要競(jìng)爭(zhēng)同一個(gè)鎖(lock),但此時(shí)鎖已經(jīng)被其他線程占據(jù),Thread-1、Thread-2競(jìng)爭(zhēng)失敗,預(yù)備進(jìn)入等待隊(duì)列,這時(shí)Thread-1、Thread-2的CPU時(shí)間片消耗完畢被掛起,而其他線程剛好釋放鎖將鎖變?yōu)闊o(wú)主狀態(tài),此時(shí)Thread-3搶鎖成功,并調(diào)用下面的doThread3()方法,連續(xù)10次獲取鎖并釋放鎖將鎖變?yōu)闊o(wú)主狀態(tài)。這種情況,就是上面說(shuō)的公平鎖無(wú)法保證線程調(diào)度的公平性,按照順序,Thread-3在Thread-1、Thread-2競(jìng)爭(zhēng)失敗后才開始競(jìng)爭(zhēng),按理鎖的分配順序應(yīng)該是Thread-1->Thread-2->Thread-3,但由于線程的調(diào)度問題,Thread-1、Thread-2尚未入隊(duì),而鎖被釋放后剛好被Thread-3“撿漏”

public void methodA() {
    try {
        lock.lock();
        //methodA body...
    } finally {
        lock.unlock();
    }
}
 
public void doThread3() {
    for (int i = 0; i < 10; i++) {
        methodA();
    }
}

除了調(diào)用ReentrantLock.lock()以阻塞的方式直到獲取鎖,ReentrantLock還提供了tryLock()和tryLock(long timeout, TimeUnit unit)兩個(gè)方法來(lái)?yè)屾i。我們看下面的代碼,相信很多同學(xué)看到這兩個(gè)方法后也能知道這兩個(gè)方法和lock()方法的區(qū)別,tryLock()會(huì)嘗試競(jìng)爭(zhēng)鎖,如果鎖已被其他線程占用,則競(jìng)爭(zhēng)失敗,返回false,如果競(jìng)爭(zhēng)成功,則返回true。tryLock(long timeout, TimeUnit unit)如果競(jìng)爭(zhēng)鎖失敗后,會(huì)先進(jìn)入等待隊(duì)列,如果在過(guò)期前能競(jìng)爭(zhēng)到鎖,則返回true,如果在過(guò)期時(shí)間內(nèi)都無(wú)法搶到鎖,則返回false。

public void methodD() {
    boolean hasLock = false;
    try {
        hasLock = lock.tryLock();//<1>非計(jì)時(shí)
        if (!hasLock) {//沒有搶到鎖則退出
            return;
        }
        //methodD body...
    } finally {
        if (hasLock) {
            lock.unlock();
        }
    }
}
 
public void methodE() {
    boolean hasLock = false;
    try {
        hasLock = lock.tryLock(5, TimeUnit.SECONDS);//<2>計(jì)時(shí)
        if (!hasLock) {//沒有搶到鎖則退出
            return;
        }
        //methodE body...
    } catch (InterruptedException e) {
        e.printStackTrace();
    } finally {
        if (hasLock) {
            lock.unlock();
        }
    }
}

需要注意的是:不管是公平鎖還是非公平鎖,不計(jì)時(shí)tryLock()都不能保證公平性,如果鎖可用,即時(shí)其他線程正在等待鎖,也會(huì)搶鎖成功。

ReentrantLock內(nèi)部會(huì)用一個(gè)int字段來(lái)標(biāo)識(shí)鎖的引用次數(shù),因此,ReentrantLock雖然作為可重入鎖,但它的最大可重入次數(shù)為2147483647(即:MaxInt32,2^31-1),不管我們是以遞歸或者是循環(huán)亦或者其他方式,一旦我們重復(fù)獲取鎖的次數(shù)超過(guò)這個(gè)次數(shù),ReentrantLock就會(huì)拋出異常。

至此,我們了解了ReentrantLock的簡(jiǎn)單應(yīng)用。下面,就請(qǐng)大家一起跟隨筆者了解ReentrantLock的實(shí)現(xiàn)原理。下面的代碼是筆者從ReentrantLock節(jié)選的部分代碼,可以看到先前我們調(diào)用加鎖(lock、lockInterruptibly、tryLock)、解鎖(unlock)的代碼,最后都會(huì)調(diào)用sync對(duì)象的方法,sync對(duì)象的類型是一個(gè)抽象類,在我們創(chuàng)建ReentrantLock對(duì)象時(shí),會(huì)根據(jù)構(gòu)造函數(shù)決定sync是公平鎖(FairSync),還是非公平鎖(NonfairSync),F(xiàn)airSync和NonfairSync都繼承自Sync,所以ReentrantLock在創(chuàng)建好具體的Sync對(duì)象后,便不再管關(guān)心公平鎖的邏輯或者是非公平鎖的邏輯,ReentrantLock只知道抽象類Sync實(shí)現(xiàn)了它所需要的功能,這個(gè)功能是公平亦或是非公平,由具體的實(shí)現(xiàn)子類來(lái)關(guān)心。

public void methodD() {
    boolean hasLock = false;
    try {
        hasLock = lock.tryLock();//<1>非計(jì)時(shí)
        if (!hasLock) {//沒有搶到鎖則退出
            return;
        }
        //methodD body...
    } finally {
        if (hasLock) {
            lock.unlock();
        }
    }
}
 
public void methodE() {
    boolean hasLock = false;
    try {
        hasLock = lock.tryLock(5, TimeUnit.SECONDS);//<2>計(jì)時(shí)
        if (!hasLock) {//沒有搶到鎖則退出
            return;
        }
        //methodE body...
    } catch (InterruptedException e) {
        e.printStackTrace();
    } finally {
        if (hasLock) {
            lock.unlock();
        }
    }
}

鑒于ReentrantLock的無(wú)參構(gòu)造函數(shù)是創(chuàng)建一個(gè)非公平鎖,可見官方更傾向于我們使用非公平鎖,這里,我們就先從非公平鎖開始介紹。

當(dāng)ReentrantLock為非公平鎖時(shí),調(diào)用lock()方法會(huì)直接調(diào)用sync.acquire(1),NonfairSync和Sync兩個(gè)類都沒有實(shí)現(xiàn)acquire(int arg),這個(gè)方法是由AbstractQueuedSynchronizer(抽象隊(duì)列同步器,下面簡(jiǎn)稱:AQS)實(shí)現(xiàn)的,也就是Sync的父類。

當(dāng)線程競(jìng)爭(zhēng)鎖時(shí),會(huì)先調(diào)用tryAcquire(arg)方法試圖占有鎖,AQS將tryAcquire(int arg)的實(shí)現(xiàn)交由子類,由子類決定是以公平還是非公平的方式占有鎖,如果競(jìng)爭(zhēng)成功tryAcquire(arg)則返回true,!tryAcquire(arg)的結(jié)果為false,于是就不會(huì)再調(diào)用<1>處后續(xù)的判斷,直接返回。如果占有鎖失敗,這里會(huì)先調(diào)用addWaiter(Node mode)方法,將當(dāng)前調(diào)用線程封裝成一個(gè)Node對(duì)象,再調(diào)用acquireQueued(final Node node, int arg)將Node對(duì)象加入到等待隊(duì)列中,并使線程陷入阻塞。

//java.util.concurrent.locks.AbstractQueuedSynchronizer#acquire
    public final void acquire(int arg) {
        if (!tryAcquire(arg) &&
            acquireQueued(addWaiter(Node.EXCLUSIVE), arg))//<1>
            selfInterrupt();
    }
 
//AbstractQueuedSynchronizer將tryAcquire(int arg)的實(shí)現(xiàn)交由子類
//java.util.concurrent.locks.AbstractQueuedSynchronizer#tryAcquire
    protected boolean tryAcquire(int arg) {
        throw new UnsupportedOperationException();
    }

我們先來(lái)看NonfairSync實(shí)現(xiàn)的tryAcquire(int acquires)方法,這里NonfairSync也是調(diào)用其父類Sync的nonfairTryAcquire(int acquires)方法。在AQS內(nèi)部會(huì)維護(hù)一個(gè)volatile int state,可重入互斥鎖會(huì)用這個(gè)字段存儲(chǔ)占有鎖的線程對(duì)鎖的引用計(jì)數(shù),即重復(fù)獲取鎖的次數(shù)。如果state為0,代表鎖目前沒有被任何線程占有,這里會(huì)用CAS的方式設(shè)置鎖的引用計(jì)數(shù),如果設(shè)置成功,則執(zhí)行<2>處的代碼將獨(dú)占線程(exclusiveOwnerThread)的引用指向當(dāng)前調(diào)用線程,然后返回true表示加鎖成功。

如果當(dāng)前state不為0,代表有線程正獨(dú)占此鎖,會(huì)在<3>處判斷當(dāng)前線程是否是獨(dú)占線程,如果是的話則在<4>處增加鎖的引用計(jì)數(shù),這里同樣是修改state的值,但不需要像<1>處那樣用CAS的方式,因?yàn)?lt;4>處的代碼只有獨(dú)占線程才可以執(zhí)行,其他線程都無(wú)法執(zhí)行。需要注意的一點(diǎn)是,state為int類型,最大值為:2^31-1,如果超過(guò)這個(gè)值state就會(huì)變?yōu)樨?fù)數(shù),就會(huì)報(bào)錯(cuò)。如果一個(gè)線程在競(jìng)爭(zhēng)鎖的時(shí)候,發(fā)現(xiàn)state不為0,且當(dāng)前線程不是獨(dú)占線程,則會(huì)返回false,表示搶鎖失敗。

//當(dāng)調(diào)用AQS的acquire(int arg)時(shí),會(huì)先調(diào)用由子類實(shí)現(xiàn)的tryAcquire(int acquires)方法
//java.util.concurrent.locks.ReentrantLock.NonfairSync#tryAcquire
    protected final boolean tryAcquire(int acquires) {
            //這里會(huì)調(diào)用父類Sync的nonfairTryAcquire(int acquires)方法
            return nonfairTryAcquire(acquires);
        }
//java.util.concurrent.locks.ReentrantLock.Sync#nonfairTryAcquire
        final boolean nonfairTryAcquire(int acquires) {
            //獲取當(dāng)前線程對(duì)象
            final Thread current = Thread.currentThread();
            //這里會(huì)獲取父類AQS的state字段,在可重入互斥鎖里,state表示占有鎖的線程的引用計(jì)數(shù)
            int c = getState();
            //如果state為0,表示目前鎖是無(wú)主狀態(tài)
            if (c == 0) {
                //如果鎖處于無(wú)主狀態(tài),則用CAS修改state,如果修改成功,表示占有鎖成功
                if (compareAndSetState(0, acquires)) {//<1>
                    //占有鎖成功后,這里會(huì)設(shè)置鎖的獨(dú)占線程
                    setExclusiveOwnerThread(current);//<2>
                    return true;
                }
            }
            else if (current == getExclusiveOwnerThread()) {//<3>如果state不為0,代表現(xiàn)在有線程占據(jù)鎖,如果請(qǐng)求鎖的線程和獨(dú)占線程是同一個(gè)線程,則增加當(dāng)前線程對(duì)鎖的引用計(jì)數(shù)
                //鎖的最大可重入次數(shù)為(2^31-1),超過(guò)這個(gè)最大范圍,int就會(huì)變?yōu)樨?fù)數(shù),判斷nextc為負(fù)數(shù)時(shí)報(bào)錯(cuò)。
                int nextc = c + acquires;
                if (nextc < 0) // overflow
                    throw new Error("Maximum lock count exceeded");
                //重新設(shè)置state的值
                setState(nextc);//<4>
                return true;
            }
            return false;
        }
 
public abstract class AbstractQueuedSynchronizer
    extends AbstractOwnableSynchronizer
    implements java.io.Serializable {
    //...
    //在可重入互斥鎖中,state代表獨(dú)占線程當(dāng)前的重入次數(shù)
    private volatile int state;
     
    protected final int getState() {
        return state;
    }
     
    protected final void setState(int newState) {
        state = newState;
    }
    //...
}
 
public abstract class AbstractOwnableSynchronizer
    implements java.io.Serializable {
    //...
    //獨(dú)占線程,當(dāng)有線程占據(jù)可重入互斥鎖時(shí),會(huì)用此字段存儲(chǔ)占有鎖的線程
    private transient Thread exclusiveOwnerThread;
     
    protected final void setExclusiveOwnerThread(Thread thread) {
        exclusiveOwnerThread = thread;
    }
     
    protected final Thread getExclusiveOwnerThread() {
        return exclusiveOwnerThread;
    }
}

按照AbstractQueuedSynchronizer.acquire(int arg)的邏輯,如果搶鎖失敗,會(huì)繼而執(zhí)行acquireQueued(addWaiter(Node.EXCLUSIVE), arg)這段代碼。這里我們需要先來(lái)了解下Node的數(shù)據(jù)結(jié)構(gòu),Node類是AQS的一個(gè)靜態(tài)內(nèi)部類。如果眼尖的同學(xué)看到下面的prev和next,一定能很快猜出這就是我們先前所說(shuō)的等待隊(duì)列,等待隊(duì)列實(shí)質(zhì)上是一個(gè)雙端鏈表,即每個(gè)節(jié)點(diǎn)都可以知道自己的前驅(qū),也可以知道自己的后繼。

//java.util.concurrent.locks.AbstractQueuedSynchronizer.Node
    static final class Node {
        static final Node SHARED = new Node();
        static final Node EXCLUSIVE = null;
        static final int CANCELLED =  1;
        static final int SIGNAL    = -1;
        //...
        volatile int waitStatus;
        volatile Node prev;
        volatile Node next;
        volatile Thread thread;
        Node nextWaiter;
        //...
        //返回當(dāng)前節(jié)點(diǎn)的前驅(qū)節(jié)點(diǎn)
        final Node predecessor() {
            Node p = prev;
            if (p == null)
                throw new NullPointerException();
            else
                return p;
        }
        Node() {}
        //...
        //創(chuàng)建Node節(jié)點(diǎn)
        Node(Node nextWaiter) {//<1>
            this.nextWaiter = nextWaiter;
            THREAD.set(this, Thread.currentThread());
        }
    }

這里簡(jiǎn)單介紹下Node的字段:

  • prev指向當(dāng)前節(jié)點(diǎn)的前驅(qū)節(jié)點(diǎn),next指向當(dāng)前節(jié)點(diǎn)的后繼節(jié)點(diǎn)。
  • thread字段在調(diào)用<1>處的構(gòu)造方法時(shí),會(huì)將thread指向當(dāng)前調(diào)用線程的Thread對(duì)象。
  • waitStatus(等待狀態(tài))初始值為0,當(dāng)waitStatus為SIGNAL(-1)時(shí),表示當(dāng)前節(jié)點(diǎn)的后繼節(jié)點(diǎn)所指向的線程(node.next.thread)陷入阻塞,當(dāng)前節(jié)點(diǎn)如果被移除(CANCELLED)或在占有鎖后要釋放鎖的時(shí)候,需要喚醒后繼節(jié)點(diǎn)的線程。這里有多種可能導(dǎo)致當(dāng)前節(jié)點(diǎn)的等待狀態(tài)變?yōu)橐瞥?,比如調(diào)用tryLock(long timeout, TimeUnit unit) 超時(shí)會(huì)獲取到鎖,或者調(diào)用lockInterruptibly()后線程被中斷。
  • nextWaiter可以用來(lái)表示一個(gè)節(jié)點(diǎn)的線程到底是獨(dú)占線程(EXCLUSIVE)還是共享線程(SHARED),獨(dú)占線程一般用于可重入互斥鎖(ReentrantLock)或者可重入讀寫鎖(ReentrantReadWriteLock)的寫鎖,而共享線程則表示當(dāng)前線程是可以和其他共享線程一起共享資源的,一般用于可重入讀寫鎖的讀鎖。

如果對(duì)上面Node字段還有不理解的地方不用心急,筆者在后面還會(huì)和大家一起深入了解這幾個(gè)字段。

在簡(jiǎn)單了解了Node的數(shù)據(jù)結(jié)構(gòu)后,我們來(lái)看看AQS是如何將一個(gè)線程封裝成一個(gè)Node對(duì)象,并將其加入到等待隊(duì)列。addWaiter(Node mode)會(huì)根據(jù)傳入的參數(shù)node,決定創(chuàng)建的節(jié)點(diǎn)是獨(dú)占節(jié)點(diǎn)還是共享節(jié)點(diǎn),先前ReentrantLock傳入的是Node.EXCLUSIVE,所以這里是獨(dú)占節(jié)點(diǎn),在執(zhí)行完<1>處的代碼后,節(jié)點(diǎn)創(chuàng)建完畢,節(jié)點(diǎn)的thread字段也保存了當(dāng)前線程對(duì)象的引用。之后會(huì)進(jìn)入<2>處的循環(huán),這里是通過(guò)CAS自旋的方式將節(jié)點(diǎn)加入到等待隊(duì)列,之所以用這種方式是因?yàn)榭赡艽嬖诙鄠€(gè)線程同時(shí)要入隊(duì)的情況,用CAS自旋保證每個(gè)節(jié)點(diǎn)的前驅(qū)和后繼的有序性。當(dāng)節(jié)點(diǎn)要入隊(duì)時(shí),會(huì)先獲取尾節(jié)點(diǎn),如果在<3>處判斷尾節(jié)點(diǎn)不為null,則將當(dāng)前節(jié)點(diǎn)的前驅(qū)指向尾節(jié)點(diǎn),并用CAS的方式設(shè)置當(dāng)前節(jié)點(diǎn)為設(shè)置為尾節(jié)點(diǎn),如果原先的尾節(jié)點(diǎn)(oldTail)的指向沒有被任何線程修改,這里用CAS將當(dāng)前節(jié)點(diǎn)設(shè)置成尾節(jié)點(diǎn)就會(huì)成功,于是原先尾節(jié)點(diǎn)的后繼指向當(dāng)前節(jié)點(diǎn),當(dāng)前節(jié)點(diǎn)入隊(duì)成功。但我們也要考慮尾節(jié)點(diǎn)為null的情況,即第一個(gè)進(jìn)入等待隊(duì)列的節(jié)點(diǎn),此時(shí)頭節(jié)點(diǎn)(header)和尾節(jié)點(diǎn)(tail)都為null,這里就會(huì)執(zhí)行<4>處的分支,進(jìn)行隊(duì)列初始化。初始化隊(duì)列的時(shí)候,同樣存在并發(fā)問題,所以這里依舊用CAS初始化頭節(jié)點(diǎn)成功,再將頭節(jié)點(diǎn)指向的Node對(duì)象賦值給尾節(jié)點(diǎn)。初始化隊(duì)列完畢后,會(huì)再開始新的一輪循環(huán),用CAS的方式嘗試將節(jié)點(diǎn)入隊(duì),入隊(duì)成功后,則返回當(dāng)前節(jié)點(diǎn)。

public abstract class AbstractQueuedSynchronizer
    extends AbstractOwnableSynchronizer
    implements java.io.Serializable {
    //...
    private transient volatile Node head;//等待隊(duì)列的頭節(jié)點(diǎn)
    private transient volatile Node tail;//等待隊(duì)列的尾節(jié)點(diǎn)
    //...
    private Node addWaiter(Node mode) {
        //為競(jìng)爭(zhēng)鎖的線程創(chuàng)建一個(gè)Node對(duì)象,并用Node.thread字段存儲(chǔ)調(diào)用線程Thread對(duì)象
        Node node = new Node(mode);//<1>
 
        for (;;) {//<2>
            Node oldTail = tail;
            if (oldTail != null) {//<3>
                node.setPrevRelaxed(oldTail);
                if (compareAndSetTail(oldTail, node)) {
                    oldTail.next = node;
                    return node;
                }
            } else {//<4>
                initializeSyncQueue();
            }
        }
    }
 
    private final void initializeSyncQueue() {
        Node h;
        if (HEAD.compareAndSet(this, null, (h = new Node())))
            tail = h;
    }
     
    private final boolean compareAndSetTail(Node expect, Node update) {
        return TAIL.compareAndSet(this, expect, update);
    }
    //...
}

在執(zhí)行完addWaiter(Node.EXCLUSIVE)確定節(jié)點(diǎn)入隊(duì)后,就要將返回節(jié)點(diǎn)傳入到方法:acquireQueued(final Node node, int arg)。之前我們說(shuō)過(guò),搶鎖失敗的節(jié)點(diǎn)會(huì)進(jìn)入一個(gè)等待隊(duì)列,等待鎖的分配,我們已經(jīng)在addWaiter(Node mode)看到線程是如何入隊(duì)的,那接下來(lái)就要看看線程是如何等待鎖的分配。在看acquireQueued(final Node node, int arg)之前,我們先來(lái)思考下如果是我們自己會(huì)如何設(shè)計(jì)將鎖分配給線程?最簡(jiǎn)單的做法是每個(gè)線程都在一個(gè)死循環(huán)中去輪詢鎖的狀態(tài),如果發(fā)現(xiàn)鎖處于無(wú)主狀態(tài)并搶鎖成功,線程則跳出循環(huán)訪問資源。但這個(gè)做法有個(gè)缺點(diǎn)就是會(huì)消耗CPU時(shí)間片,尤其對(duì)于一些優(yōu)先級(jí)不高的線程,相比于優(yōu)先級(jí)高的線程它們可能永遠(yuǎn)無(wú)法競(jìng)爭(zhēng)到鎖,永遠(yuǎn)訪問不到資源處于饑餓狀態(tài)。那么有沒有相比死循環(huán)更好的做法呢?我們是否可以先把一個(gè)入隊(duì)的線程阻塞起來(lái),先讓它不要消耗寶貴的CPU時(shí)間片,當(dāng)占據(jù)鎖的線程完全釋放鎖(state變?yōu)?)時(shí),則去喚醒隊(duì)列中等待時(shí)長(zhǎng)最長(zhǎng)的線程,這樣也不用擔(dān)心優(yōu)先級(jí)低的線程無(wú)法與優(yōu)先級(jí)高的線程競(jìng)爭(zhēng)鎖,導(dǎo)致處于饑餓狀態(tài),一舉兩得。

這里我們還要再加深下對(duì)等待隊(duì)列Node的理解才能往下看acquireQueued(final Node node, int arg),大家思考下,Node中的thread字段是用來(lái)指向競(jìng)爭(zhēng)鎖的線程對(duì)象,通過(guò)這個(gè)對(duì)象,我們可以用釋放鎖的線程喚醒等待鎖的線程,占用鎖的線程在完全釋放鎖將鎖變?yōu)闊o(wú)主狀態(tài)后,喚醒等待鎖的線程,這個(gè)等待鎖的線程如果成功占據(jù)了鎖,是否可以將本身線程中Node.thread置為null?此刻線程已經(jīng)占據(jù)了鎖,它不會(huì)再陷入阻塞,也不需要有其他的線程來(lái)喚醒自身。所以等待隊(duì)列的頭節(jié)點(diǎn)的thread(header.thread)字段永遠(yuǎn)為null,因?yàn)殒i被頭節(jié)點(diǎn)的線程所占用。

當(dāng)然,也可能出現(xiàn)鎖被占用但頭節(jié)點(diǎn)(header)本身就為null,這種情況一般出現(xiàn)在我們初始化好一個(gè)ReentrantLock后,只有一個(gè)線程占有了鎖,此時(shí)調(diào)用tryAcquire(int acquires)會(huì)調(diào)用ReentrantLock.Sync.nonfairTryAcquire(int acquires)方法,這個(gè)方法只會(huì)簡(jiǎn)單修改state狀態(tài),并不會(huì)新增一個(gè)頭節(jié)點(diǎn)。除非鎖已有線程占據(jù),且出現(xiàn)新的線程競(jìng)爭(zhēng)鎖,這時(shí)候新的線程在進(jìn)入等待隊(duì)列的時(shí)候,會(huì)初始化隊(duì)列,為本身占據(jù)鎖的線程補(bǔ)上一個(gè)頭節(jié)點(diǎn),初始化隊(duì)列的時(shí)候調(diào)用的是Node的無(wú)參構(gòu)造方法,所以頭節(jié)點(diǎn)的thread字段為null,表示鎖被當(dāng)前頭節(jié)點(diǎn)原先指向的線程所占據(jù)。

在了解這些基本知識(shí)后,下面我們終于可以來(lái)看看大家迫不及待的acquireQueued(final Node node, int arg)了。當(dāng)把封裝了當(dāng)前線程的Node對(duì)象傳入到acquireQueued(final Node node, int arg)方法時(shí),并不會(huì)立即阻塞當(dāng)前線程等待其他線程喚醒。這里會(huì)先在<1>處獲取當(dāng)前節(jié)點(diǎn)的前驅(qū)節(jié)點(diǎn)p,判斷p是不是頭節(jié)點(diǎn),如果p是頭節(jié)點(diǎn),則當(dāng)前線程即有占有鎖的可能。因?yàn)檎紦?jù)鎖的線程會(huì)先釋放鎖,再通知隊(duì)列中的線程搶鎖。所以會(huì)存在當(dāng)前節(jié)點(diǎn)入隊(duì)前鎖已被釋放的情況,于是判斷前驅(qū)節(jié)點(diǎn)p是頭節(jié)點(diǎn),會(huì)再調(diào)用tryAcquire(int acquires)方法搶鎖,如果搶鎖成功,就可以按照我們上面所說(shuō)的套路,調(diào)用setHead(Node node)將當(dāng)前節(jié)點(diǎn)設(shè)置為頭節(jié)點(diǎn),設(shè)置當(dāng)前節(jié)點(diǎn)的線程引用為null,然后返回。

如果當(dāng)前節(jié)點(diǎn)的前驅(qū)節(jié)點(diǎn)不是頭節(jié)點(diǎn),這里就要調(diào)用shouldParkAfterFailedAcquire(Node pred, Node node)設(shè)置前驅(qū)節(jié)點(diǎn)的等待狀態(tài)(waitStatus),先前說(shuō)過(guò),這個(gè)等待狀態(tài)可以用來(lái)表示下個(gè)節(jié)點(diǎn)的阻塞狀態(tài)。假設(shè)有一個(gè)鎖已經(jīng)被其他線程占有,Thread-1、Thread-2要來(lái)?yè)屾i,此時(shí)必然是搶鎖失敗的,這里會(huì)把Thread-1、Thread-2分別封裝成Node1和Node2并進(jìn)行入隊(duì),Node1和Node2初始的等待狀態(tài)都為0,假定Node1先Node2入隊(duì),Node1為Node2的前驅(qū)節(jié)點(diǎn)(即:Node2.prev=Node1),Node1不是頭節(jié)點(diǎn),所以不會(huì)去搶鎖,這里直接進(jìn)入<2>處分支的shouldParkAfterFailedAcquire(Node pred, Node node)方法,Node1的初始等待狀態(tài)為0,所以<3>處和<5>處的分支是進(jìn)不去的,只能進(jìn)入<4>處的分支,將Node1的等待狀態(tài)設(shè)置為SIGNAL,表示Node1的后繼節(jié)點(diǎn)處于等待喚醒狀態(tài),然后返回false,于是<2>處的判斷不成立,又開始新的一輪循環(huán),假定頭節(jié)點(diǎn)的線程依舊沒釋放鎖,Node1依舊不是頭節(jié)點(diǎn),還是直接執(zhí)行shouldParkAfterFailedAcquire(Node pred, Node node)方法,此時(shí)判斷Node2的前驅(qū)節(jié)點(diǎn)Node1的等待狀態(tài)為-1,表示可以阻塞Node1后繼節(jié)點(diǎn)Node2所指向的線程,所以這里會(huì)返回true,進(jìn)入<2>處的分支,調(diào)用parkAndCheckInterrupt()方法,在這個(gè)方法中會(huì)調(diào)用LockSupport.park(Object blocker)阻塞當(dāng)前的調(diào)用線程,直到有其他線程調(diào)用LockSupport.unpark(Node2.thread)喚醒Node2被阻塞的線程,或Node2.thread被中斷才會(huì)退出parkAndCheckInterrupt()。我們注意到在<5>處有一個(gè)判斷,前驅(qū)節(jié)點(diǎn)的等待狀態(tài)>0,一般狀態(tài)為CANCELLED(1),表示前驅(qū)節(jié)點(diǎn)被移除。之所以會(huì)存在被移除的節(jié)點(diǎn),是因?yàn)槲覀兛赡芤詔ryLock(long timeout, TimeUnit unit)的方式往等待隊(duì)列中添加節(jié)點(diǎn),如果超時(shí)還未獲得鎖,這個(gè)節(jié)點(diǎn)就要被移除;我們還可能用lockInterruptibly()的方式往等待隊(duì)列中添加節(jié)點(diǎn),如果節(jié)點(diǎn)所對(duì)應(yīng)的線程被中斷,這個(gè)節(jié)點(diǎn)也處于被移除狀態(tài)。所以<5>處如果發(fā)現(xiàn)前驅(qū)節(jié)點(diǎn)的等待狀態(tài)大于0,會(huì)一直往前驅(qū)節(jié)點(diǎn)遍歷直到找到等待狀態(tài)<=0的節(jié)點(diǎn)將其作為前驅(qū)節(jié)點(diǎn),并將前驅(qū)節(jié)點(diǎn)的后繼指向當(dāng)前節(jié)點(diǎn)。要注意的是,等待狀態(tài)為-1時(shí),代表當(dāng)前節(jié)點(diǎn)的后繼節(jié)點(diǎn)等待喚醒,>0的時(shí)候,代表當(dāng)前節(jié)點(diǎn)被移除,前者的狀態(tài)與后繼節(jié)點(diǎn)有關(guān),后者的狀態(tài)僅與自身有關(guān)。如果在自旋期間線程出現(xiàn)其他異常,則會(huì)調(diào)用<6>處的代碼將節(jié)點(diǎn)從等待隊(duì)列移除,并拋出異常。cancelAcquire(Node node)會(huì)在后面介紹,這里我們只要先知道這是一個(gè)將節(jié)點(diǎn)從隊(duì)列中移除的方法。

public abstract class AbstractQueuedSynchronizer
    extends AbstractOwnableSynchronizer
    implements java.io.Serializable {
    //...
    private transient volatile Node head;
    //...
    final boolean acquireQueued(final Node node, int arg) {
        boolean interrupted = false;
        try {
            for (;;) {
                final Node p = node.predecessor();//<1>
                if (p == head && tryAcquire(arg)) {
                    setHead(node);
                    p.next = null; // help GC
                    return interrupted;
                }
                if (shouldParkAfterFailedAcquire(p, node))//<2>
                    interrupted |= parkAndCheckInterrupt();
            }
        } catch (Throwable t) {
            cancelAcquire(node);//<6>
            if (interrupted)
                selfInterrupt();
            throw t;
        }
    }
    //...
    //設(shè)置當(dāng)前節(jié)點(diǎn)為頭節(jié)點(diǎn),此時(shí)可以清空頭節(jié)點(diǎn)指向的線程引用
    private void setHead(Node node) {
        head = node;
        node.thread = null;
        node.prev = null;
    }
    //...
    private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) {
        int ws = pred.waitStatus;
        if (ws == Node.SIGNAL)//<3>
            /*
             * This node has already set status asking a release
             * to signal it, so it can safely park.
             */
            return true;
        if (ws > 0) {//<5>
            /*
             * Predecessor was cancelled. Skip over predecessors and
             * indicate retry.
             */
            do {
                node.prev = pred = pred.prev;
            } while (pred.waitStatus > 0);
            pred.next = node;
        } else {//<4>
            /*
             * waitStatus must be 0 or PROPAGATE.  Indicate that we
             * need a signal, but don't park yet.  Caller will need to
             * retry to make sure it cannot acquire before parking.
             */
            pred.compareAndSetWaitStatus(ws, Node.SIGNAL);
        }
        return false;
    }
    //...
    private final boolean parkAndCheckInterrupt() {
        //阻塞調(diào)用線程,可調(diào)用LockSupport.unpark(Thread thread)喚醒或由線程中斷喚醒。
        LockSupport.park(this);
        //返回線程是否由中斷喚醒,返回true為被中斷喚醒,但此方法會(huì)清除線程的中斷標(biāo)記
        return Thread.interrupted();
    }
    //...
}

能從boolean acquireQueued(final Node node, int arg)方法中返回的線程,都是成功占有鎖的線程,但返回結(jié)果分當(dāng)前線程是否被中斷,true為被中斷??赡艽嬖谶@樣一種情況,前一個(gè)線程釋放鎖完畢后,即將喚醒后一個(gè)線程,此時(shí)后一個(gè)線程被中斷喚醒,后一個(gè)線程發(fā)現(xiàn)其Node節(jié)點(diǎn)的前驅(qū)節(jié)點(diǎn)為頭節(jié)點(diǎn),且鎖為無(wú)主狀態(tài),于是搶鎖成功直接返回。這里要標(biāo)記線程的中斷狀態(tài)interrupted,因?yàn)榫€程會(huì)從parkAndCheckInterrupt()中被喚醒,最后會(huì)執(zhí)行Thread.interrupted()返回當(dāng)前線程是否由中斷喚醒,但Thread.interrupted()會(huì)清除中斷標(biāo)記,所以在占據(jù)鎖之后會(huì)根據(jù)返回的interrupted狀態(tài),決定是否設(shè)置線程的中斷狀態(tài)。如果一個(gè)線程在調(diào)用acquireQueued(final Node node, int arg)方法的后都未被中斷,直到前一個(gè)線程調(diào)用LockSupport.unpark(Thread thread)喚醒該線程,那么這個(gè)線程就不是用中斷的形式喚醒,也就不用設(shè)置線程的中斷狀態(tài)。

public abstract class AbstractQueuedSynchronizer
    extends AbstractOwnableSynchronizer
    implements java.io.Serializable {
    //...
    public final void acquire(int arg) {
        if (!tryAcquire(arg) &&
            acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
            //根據(jù)acquireQueued()的返回,決定是否設(shè)置線程的中斷標(biāo)記
            selfInterrupt();
    }
    //...
    static void selfInterrupt() {
        Thread.currentThread().interrupt();
    }
    //...
}

到此這篇關(guān)于Java源碼解析之詳解ReentrantLock的文章就介紹到這了,更多相關(guān)ReentrantLock源碼解析內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!

相關(guān)文章

  • Java中數(shù)組array和列表list相互轉(zhuǎn)換

    Java中數(shù)組array和列表list相互轉(zhuǎn)換

    這篇文章主要介紹了Java中數(shù)組array和列表list相互轉(zhuǎn)換,在Java中,可以將數(shù)組(array)和列表(list)相互轉(zhuǎn)換,但需要注意一些細(xì)節(jié)和限制,本文通過(guò)實(shí)例代碼給大家介紹的非常詳細(xì),需要的朋友可以參考下
    2023-09-09
  • idea中Tomcat啟動(dòng)失敗的解決

    idea中Tomcat啟動(dòng)失敗的解決

    這篇文章主要介紹了idea中Tomcat啟動(dòng)失敗的解決,具有很好的參考價(jià)值,希望對(duì)大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教
    2024-09-09
  • java IO數(shù)據(jù)操作流、對(duì)象序列化、壓縮流代碼解析

    java IO數(shù)據(jù)操作流、對(duì)象序列化、壓縮流代碼解析

    這篇文章主要介紹了java IO數(shù)據(jù)操作流、對(duì)象序列化、壓縮流代碼解析,具有一定借鑒價(jià)值,需要的朋友可以參考下
    2018-01-01
  • 詳細(xì)聊聊RabbitMQ竟無(wú)法反序列化List問題

    詳細(xì)聊聊RabbitMQ竟無(wú)法反序列化List問題

    這篇文章主要給大家介紹了關(guān)于RabbitMQ竟無(wú)法反序列化List的相關(guān)資料,文中通過(guò)示例代碼將問題以及解決的過(guò)程介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下
    2021-09-09
  • Java并發(fā)編程學(xué)習(xí)之ThreadLocal源碼詳析

    Java并發(fā)編程學(xué)習(xí)之ThreadLocal源碼詳析

    這篇文章主要給大家介紹了關(guān)于Java并發(fā)編程學(xué)習(xí)之源碼分析ThreadLocal的相關(guān)資料,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧
    2018-06-06
  • java實(shí)現(xiàn)文本框和文本區(qū)的輸入輸出

    java實(shí)現(xiàn)文本框和文本區(qū)的輸入輸出

    這篇文章主要介紹了java實(shí)現(xiàn)文本框和文本區(qū)的輸入輸出的方法和具體示例,有需要的小伙伴可以參考下。
    2015-06-06
  • Java使用x-www-form-urlencoded發(fā)請(qǐng)求方式

    Java使用x-www-form-urlencoded發(fā)請(qǐng)求方式

    在開發(fā)中經(jīng)常使用JSON格式,但遇到x-www-form-urlencoded格式時(shí),可以通過(guò)重新封裝處理,POSTMan和APIpost工具中對(duì)此編碼的稱呼不同,分別是x-www-form-urlencoded和urlencoded,分享這些經(jīng)驗(yàn)希望對(duì)他人有所幫助
    2024-09-09
  • 使用Lombok時(shí)@JsonIgnore注解失效解決方案

    使用Lombok時(shí)@JsonIgnore注解失效解決方案

    這篇文章主要為大家介紹了使用Lombok時(shí)@JsonIgnore注解失效問題解決,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪
    2023-06-06
  • java加密算法分享(rsa解密、對(duì)稱加密、md5加密)

    java加密算法分享(rsa解密、對(duì)稱加密、md5加密)

    這篇文章主要介紹了java加密算法,包括rsa解密、對(duì)稱加密、md5加密等,需要的朋友可以參考下
    2014-05-05
  • Spring中@RequestParam、@RequestBody和@PathVariable的用法詳解

    Spring中@RequestParam、@RequestBody和@PathVariable的用法詳解

    這篇文章主要介紹了Spring中@RequestParam、@RequestBody和@PathVariable的用法詳解,后端使用集合來(lái)接受參數(shù),靈活性較好,如果url中沒有對(duì)參數(shù)賦key值,后端在接收時(shí),會(huì)根據(jù)參數(shù)值的類型附,賦一個(gè)初始key,需要的朋友可以參考下
    2024-01-01

最新評(píng)論