軟件測(cè)試之python自動(dòng)化測(cè)試5(web/app/接口自動(dòng)化/自動(dòng)化框架)57期
可重入鎖獲取和釋放鎖的過(guò)程分析
目的
軟件測(cè)試之python自動(dòng)化測(cè)試5(web/app/接口自動(dòng)化/自動(dòng)化框架)57期
download:https://www.zxit666.com/5679/
了解ReentrantLock獲取和釋放鎖的過(guò)程。獲取鎖定進(jìn)程
整個(gè)過(guò)程可以總結(jié)為做兩件事。
成功獲取鎖,在當(dāng)前線程中執(zhí)行其他事情;
鎖獲取失敗,當(dāng)前線程加入同步隊(duì)列,同時(shí)阻塞當(dāng)前線程。
當(dāng)?shù)谝粋€(gè)線程(thead0)進(jìn)來(lái)時(shí),通過(guò)CAS將state屬性更改為1。如果成功,通過(guò)setExclusiveOwnerThread()方法將exclusiveOwnerThread設(shè)置為當(dāng)前線程。
此時(shí),當(dāng)?shù)诙€(gè)線程(thead1)進(jìn)來(lái)并通過(guò)CAS將state屬性更改為1時(shí),它將失敗。這時(shí),它進(jìn)入acquire()方法。最終會(huì)得出以下方法:首先通過(guò)tryAcquire()方法再次嘗試獲取鎖。
tryAcquire()方法仍然通過(guò)CAS獲得鎖。此時(shí),鎖資源仍由第二個(gè)線程持有,因此它將返回false?,F(xiàn)在看看acquire()方法中的if判斷。
此時(shí),獲取排隊(duì)(添加服務(wù)員(節(jié)點(diǎn)。exclusive)、arg)進(jìn)行判斷。這里需要執(zhí)行兩個(gè)方法addWaiter()和acquireQueued()。首先看addWaiter()方法。這種方法,我們需要注意以下四點(diǎn)。
首先會(huì)構(gòu)造一個(gè)node節(jié)點(diǎn)(節(jié)點(diǎn)內(nèi)部細(xì)節(jié)見(jiàn)其構(gòu)造方法)。
如果tail(同步隊(duì)列尾節(jié)點(diǎn)指針)不為空,即同步隊(duì)列不為空,那么步驟1中構(gòu)造的節(jié)點(diǎn)將通過(guò)尾插入添加到隊(duì)列中,然后返回。
如果同步隊(duì)列為空,那么執(zhí)行enq()方法,它為我們做了兩件事。
如果同步隊(duì)列為空,則初始化隊(duì)列。
初始化隊(duì)列后,將節(jié)點(diǎn)node入隊(duì)。
通過(guò)addWaiter()方法和enq()方法,我們還可以看到,AQS中的同步隊(duì)列是通過(guò)一個(gè)雙向鏈表來(lái)實(shí)現(xiàn)的。當(dāng)一個(gè)節(jié)點(diǎn)入隊(duì)和出隊(duì)時(shí),需要修改兩個(gè)指針(prev和next)。
addWaiter()方法執(zhí)行后,我們通過(guò)下圖大致看一下此時(shí)同步隊(duì)列中指向的節(jié)點(diǎn)。在這里,如果不太理解,可以回頭看看enq()方法的執(zhí)行流程。
執(zhí)行addWaiter()方法后,它將在入隊(duì)后返回到新節(jié)點(diǎn)。然后開(kāi)始執(zhí)行acquireQueued()方法。這個(gè)方法做了五件事。
獲取當(dāng)前節(jié)點(diǎn)的前任節(jié)點(diǎn)p。
如果P是頭節(jié)點(diǎn),再?lài)L試獲取鎖,如果成功獲取鎖,就可以跳出循環(huán)了。
無(wú)法獲取鎖。由shouldParkAfterFailedAcquire()將waitSatus改為-1(為什么改為-1?原因可以在AQS的源代碼中找到,后續(xù)獲取鎖的過(guò)程會(huì)遵循這個(gè)邏輯)
4.ParkandChekingInterrupt()方法會(huì)阻塞當(dāng)前線程,同時(shí)可以返回當(dāng)前線程的中斷狀態(tài)(Thread.interrupted()會(huì)清除中斷標(biāo)志位)。
經(jīng)過(guò)上面的操作,我們可以知道線程1沒(méi)有獲得鎖,被添加到同步隊(duì)列中,并阻塞了它??偨Y(jié)起來(lái)就是四個(gè)字:“入隊(duì)”“封殺”。此時(shí),我們的同步隊(duì)列也變成如下所示。執(zhí)行后與上述addWaiter()方法相比,只是thread 1節(jié)點(diǎn)的前任節(jié)點(diǎn),waitStaus設(shè)置為-1。
釋放鎖定過(guò)程
整個(gè)過(guò)程(僅考慮解鎖成功)可以總結(jié)為三件事。
釋放鎖資源;
喚醒同步隊(duì)列中頭節(jié)點(diǎn)之后的節(jié)點(diǎn)對(duì)應(yīng)的線程。
步驟2被喚醒的線程試圖競(jìng)爭(zhēng)鎖。如果競(jìng)爭(zhēng)成功,則更新同步隊(duì)列(即頭節(jié)點(diǎn)出列)。
當(dāng)?shù)谝粋€(gè)線程(thread0)執(zhí)行自己的業(yè)務(wù)流程時(shí),它將釋放鎖。至此,我們來(lái)看一下解除鎖的過(guò)程。調(diào)用unlock()方法可以釋放鎖。需要注意的是,為了避免死鎖,這個(gè)方法的調(diào)用需要放在最后的代碼塊中。
當(dāng)thread0釋放鎖時(shí),它調(diào)用release()方法,該方法主要做兩件事。
調(diào)用tryRelease()方法來(lái)釋放鎖。
在方法內(nèi)部,狀態(tài)被設(shè)置為0,exclusiveOwnerThread被setExclusiveOwnerThread()方法設(shè)置為null時(shí),意味著此時(shí)鎖資源被釋放,沒(méi)有線程持有鎖資源。
如果第一步返回true,即鎖被成功釋放,那么開(kāi)始第二步。第二步是首先獲取同步隊(duì)列的頭節(jié)點(diǎn),并檢查其waitStatus屬性。這里,讓我們把剛剛獲得鎖的節(jié)點(diǎn)的情況放在同步隊(duì)列中。此時(shí),我們的head節(jié)點(diǎn)的waitStatus為-1,所以我們將進(jìn)入unparksuccess()方法。
parksuccess()方法主要做以下三件事。注意第三步。根據(jù)我們當(dāng)前的同步隊(duì)列,LockSupport.unpark()方法將喚醒線程1。
當(dāng)執(zhí)行unparksuccess()方法中的LockSupport.unpark()方法時(shí),線程1(thread1)將被喚醒。線程1被喚醒后,我們來(lái)看看線程1的執(zhí)行情況。此時(shí),線程1將繼續(xù)執(zhí)行acquireQueued()方法中的for循環(huán)(注意:這是一個(gè)無(wú)限循環(huán))。執(zhí)行順序與鎖獲取相同,但當(dāng)與鎖獲取不同時(shí),步驟2中的鎖獲取將會(huì)成功(因?yàn)閠hread0已經(jīng)釋放了鎖)。
成功獲取鎖后,當(dāng)前同步隊(duì)列中的頭節(jié)點(diǎn)將出隊(duì)。此時(shí),同步隊(duì)列中節(jié)點(diǎn)的情況如下。
至此,釋放鎖的邏輯完成。其實(shí)總結(jié)起來(lái)很簡(jiǎn)單。首先釋放鎖資源,然后喚醒同步隊(duì)列中頭節(jié)點(diǎn)的下一個(gè)節(jié)點(diǎn)對(duì)應(yīng)的線程,最后更新同步隊(duì)列(出列)。
摘要
以上是ReentrantLock獲取和釋放鎖的一般過(guò)程。通過(guò)本文,讀者對(duì)ReentrantLock的鎖的獲取和釋放過(guò)程有了一個(gè)大致的了解。細(xì)心的讀者可能會(huì)發(fā)現(xiàn),在獲取鎖時(shí),acquireQueued()方法中有一個(gè)cancelAcquire()方法的調(diào)用邏輯。