<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          AQS基礎(chǔ)——多圖詳解CLH鎖的原理與實(shí)現(xiàn)

          共 5529字,需瀏覽 12分鐘

           ·

          2020-09-02 13:12

          JDK1.8源碼分析項(xiàng)目(中文注釋?zhuān)?/strong>Github地址:

          https://github.com/yuanmabiji/jdk1.8-sourcecode-blogs

          1 什么是自旋鎖和互斥鎖?

          由于CLH鎖是一種自旋鎖,那么我們先來(lái)看看自旋鎖是什么?

          自旋鎖說(shuō)白了也是一種互斥鎖,只不過(guò)沒(méi)有搶到鎖的線程會(huì)一直自旋等待鎖的釋放,處于busy-waiting的狀態(tài),此時(shí)等待鎖的線程不會(huì)進(jìn)入休眠狀態(tài),而是一直忙等待浪費(fèi)CPU周期。因此自旋鎖適用于鎖占用時(shí)間短的場(chǎng)合。

          這里談到了自旋鎖,那么我們也順便說(shuō)下互斥鎖。這里的互斥鎖說(shuō)的是傳統(tǒng)意義的互斥鎖,就是多個(gè)線程并發(fā)競(jìng)爭(zhēng)鎖的時(shí)候,沒(méi)有搶到鎖的線程會(huì)進(jìn)入休眠狀態(tài)即sleep-waiting,當(dāng)鎖被釋放的時(shí)候,處于休眠狀態(tài)的一個(gè)線程會(huì)再次獲取到鎖。缺點(diǎn)就是這一些列過(guò)程需要線程切換,需要執(zhí)行很多CPU指令,同樣需要時(shí)間。如果CPU執(zhí)行線程切換的時(shí)間比鎖占用的時(shí)間還長(zhǎng),那么可能還不如使用自旋鎖。因此互斥鎖適用于鎖占用時(shí)間長(zhǎng)的場(chǎng)合。

          2 什么是CLH鎖?

          CLH鎖其實(shí)就是一種是基于邏輯隊(duì)列非線程饑餓的一種自旋公平鎖,由于是 Craig、Landin 和 Hagersten三位大佬的發(fā)明,因此命名為CLH鎖。

          CLH鎖原理如下:

          1. 首先有一個(gè)尾節(jié)點(diǎn)指針,通過(guò)這個(gè)尾結(jié)點(diǎn)指針來(lái)構(gòu)建等待線程的邏輯隊(duì)列,因此能確保線程線程先到先服務(wù)的公平性,因此尾指針可以說(shuō)是構(gòu)建邏輯隊(duì)列的橋梁;此外這個(gè)尾節(jié)點(diǎn)指針是原子引用類(lèi)型,避免了多線程并發(fā)操作的線程安全性問(wèn)題;
          2. 通過(guò)等待鎖的每個(gè)線程在自己的某個(gè)變量上自旋等待,這個(gè)變量將由前一個(gè)線程寫(xiě)入。由于某個(gè)線程獲取鎖操作時(shí)總是通過(guò)尾節(jié)點(diǎn)指針獲取到前一線程寫(xiě)入的變量,而尾節(jié)點(diǎn)指針又是原子引用類(lèi)型,因此確保了這個(gè)變量獲取出來(lái)總是線程安全的。

          這么說(shuō)肯定很抽象,有些小伙伴可能不理解,沒(méi)關(guān)系,我們心中可以有個(gè)概念即可,后面我們會(huì)一步一圖來(lái)徹徹底底把CLH鎖弄明白。

          3 為什么要學(xué)習(xí)CLH鎖?

          好了,前面我們對(duì)CLH鎖有了一個(gè)概念后,那么我們?yōu)槭裁匆獙W(xué)習(xí)CLH鎖呢?

          研究過(guò)AQS源碼的小伙伴們應(yīng)該知道,AQS是JUC的核心,而CLH鎖又是AQS的基礎(chǔ),說(shuō)核心也不為過(guò),因?yàn)锳QS就是用了變種的CLH鎖。如果要學(xué)好Java并發(fā)編程,那么必定要學(xué)好JUC;學(xué)好JUC,必定要先學(xué)好AQS;學(xué)好AQS,那么必定先學(xué)好CLH。因此,這就是我們?yōu)槭裁匆獙W(xué)習(xí)CLH鎖的原因。

          4 CLH鎖詳解

          那么,下面我們先來(lái)看CLH鎖實(shí)現(xiàn)代碼,然后通過(guò)一步一圖來(lái)詳解CLH鎖。

          //?CLHLock.java

          public?class?CLHLock?{
          ????/**
          ?????*?CLH鎖節(jié)點(diǎn)
          ?????*/

          ????private?static?class?CLHNode?{
          ????????//?鎖狀態(tài):默認(rèn)為false,表示線程沒(méi)有獲取到鎖;true表示線程獲取到鎖或正在等待
          ????????//?為了保證locked狀態(tài)是線程間可見(jiàn)的,因此用volatile關(guān)鍵字修飾
          ????????volatile?boolean?locked?=?false;
          ????}
          ????//?尾結(jié)點(diǎn),總是指向最后一個(gè)CLHNode節(jié)點(diǎn)
          ????//?【注意】這里用了java的原子系列之AtomicReference,能保證原子更新
          ????private?final?AtomicReference?tailNode;
          ????//?當(dāng)前節(jié)點(diǎn)的前繼節(jié)點(diǎn)
          ????private?final?ThreadLocal?predNode;
          ????//?當(dāng)前節(jié)點(diǎn)
          ????private?final?ThreadLocal?curNode;

          ????//?CLHLock構(gòu)造函數(shù),用于新建CLH鎖節(jié)點(diǎn)時(shí)做一些初始化邏輯
          ????public?CLHLock()?{
          ????????//?初始化時(shí)尾結(jié)點(diǎn)指向一個(gè)空的CLH節(jié)點(diǎn)
          ????????tailNode?=?new?AtomicReference<>(new?CLHNode());
          ????????//?初始化當(dāng)前的CLH節(jié)點(diǎn)
          ????????curNode?=?new?ThreadLocal()?{
          ????????????@Override
          ????????????protected?CLHNode?initialValue()?{
          ????????????????return?new?CLHNode();
          ????????????}
          ????????};
          ????????//?初始化前繼節(jié)點(diǎn),注意此時(shí)前繼節(jié)點(diǎn)沒(méi)有存儲(chǔ)CLHNode對(duì)象,存儲(chǔ)的是null
          ????????predNode?=?new?ThreadLocal();
          ????}

          ????/**
          ?????*?獲取鎖
          ?????*/

          ????public?void?lock()?{
          ????????//?取出當(dāng)前線程ThreadLocal存儲(chǔ)的當(dāng)前節(jié)點(diǎn),初始化值總是一個(gè)新建的CLHNode,locked狀態(tài)為false。
          ????????CLHNode?currNode?=?curNode.get();
          ????????//?此時(shí)把lock狀態(tài)置為true,表示一個(gè)有效狀態(tài),
          ????????//?即獲取到了鎖或正在等待鎖的狀態(tài)
          ????????currNode.locked?=?true;
          ????????//?當(dāng)一個(gè)線程到來(lái)時(shí),總是將尾結(jié)點(diǎn)取出來(lái)賦值給當(dāng)前線程的前繼節(jié)點(diǎn);
          ????????//?然后再把當(dāng)前線程的當(dāng)前節(jié)點(diǎn)賦值給尾節(jié)點(diǎn)
          ????????//?【注意】在多線程并發(fā)情況下,這里通過(guò)AtomicReference類(lèi)能防止并發(fā)問(wèn)題
          ????????//?【注意】哪個(gè)線程先執(zhí)行到這里就會(huì)先執(zhí)行predNode.set(preNode);語(yǔ)句,因此構(gòu)建了一條邏輯線程等待鏈
          ????????//?這條鏈避免了線程饑餓現(xiàn)象發(fā)生
          ????????CLHNode?preNode?=?tailNode.getAndSet(currNode);
          ????????//?將剛獲取的尾結(jié)點(diǎn)(前一線程的當(dāng)前節(jié)點(diǎn))付給當(dāng)前線程的前繼節(jié)點(diǎn)ThreadLocal
          ????????//?【思考】這句代碼也可以去掉嗎,如果去掉有影響嗎?
          ????????predNode.set(preNode);
          ????????//?【1】若前繼節(jié)點(diǎn)的locked狀態(tài)為false,則表示獲取到了鎖,不用自旋等待;
          ????????//?【2】若前繼節(jié)點(diǎn)的locked狀態(tài)為true,則表示前一線程獲取到了鎖或者正在等待,自旋等待
          ????????while?(preNode.locked)?{
          ????????????System.out.println("線程"?+?Thread.currentThread().getName()?+?"沒(méi)能獲取到鎖,進(jìn)行自旋等待。。。");
          ????????}
          ????????//?能執(zhí)行到這里,說(shuō)明當(dāng)前線程獲取到了鎖
          ????????System.out.println("線程"?+?Thread.currentThread().getName()?+?"獲取到了鎖?。。?);
          ????}

          ????/**
          ?????*?釋放鎖
          ?????*/

          ????public?void?unLock()?{
          ????????//?獲取當(dāng)前線程的當(dāng)前節(jié)點(diǎn)
          ????????CLHNode?node?=?curNode.get();
          ????????//?進(jìn)行解鎖操作
          ????????//?這里將locked至為false,此時(shí)執(zhí)行了lock方法正在自旋等待的后繼節(jié)點(diǎn)將會(huì)獲取到鎖
          ????????//?【注意】而不是所有正在自旋等待的線程去并發(fā)競(jìng)爭(zhēng)鎖
          ????????node.locked?=?false;
          ????????System.out.println("線程"?+?Thread.currentThread().getName()?+?"釋放了鎖!??!");
          ????????//?小伙伴們可以思考下,下面兩句代碼的作用是什么??
          ????????CLHNode?newCurNode?=?new?CLHNode();
          ????????curNode.set(newCurNode);

          ????????//?【優(yōu)化】能提高GC效率和節(jié)省內(nèi)存空間,請(qǐng)思考:這是為什么?
          ????????//?curNode.set(predNode.get());
          ????}
          }

          4.1 CLH鎖的初始化邏輯

          通過(guò)上面代碼,我們縷一縷CLH鎖的初始化邏輯先:

          1. 定義了一個(gè)CLHNode節(jié)點(diǎn),里面有一個(gè)locked屬性,表示線程線程是否獲得鎖,默認(rèn)為false。false表示線程沒(méi)有獲取到鎖或已經(jīng)釋放鎖;true表示線程獲取到了鎖或者正在自旋等待。

          注意,為了保證locked屬性線程間可見(jiàn),該屬性被volatile修飾。

          1. CLHLock有三個(gè)重要的成員變量尾節(jié)點(diǎn)指針tailNode,當(dāng)前線程的前繼節(jié)點(diǎn)preNode和當(dāng)前節(jié)點(diǎn)curNode。其中tailNodeAtomicReference類(lèi)型,目的是為了保證尾節(jié)點(diǎn)的線程安全性;此外,preNodecurNode都是ThreadLocal類(lèi)型即線程本地變量類(lèi)型,用來(lái)保存每個(gè)線程的前繼CLHNode和當(dāng)前CLHNode節(jié)點(diǎn)。
          2. 最重要的是我們新建一把CLHLock對(duì)象時(shí),此時(shí)會(huì)執(zhí)行構(gòu)造函數(shù)里面的初始化邏輯。此時(shí)給尾指針tailNode和當(dāng)前節(jié)點(diǎn)curNode初始化一個(gè)locked狀態(tài)為falseCLHNode節(jié)點(diǎn),此時(shí)前繼節(jié)點(diǎn)preNode存儲(chǔ)的是null

          4.2 CLH鎖的加鎖過(guò)程

          我們?cè)賮?lái)看看CLH鎖的加鎖過(guò)程,下面再貼一遍加鎖lock方法的代碼:

          //?CLHLock.java

          /**
          ?*?獲取鎖
          ?*/

          public?void?lock()?{
          ????//?取出當(dāng)前線程ThreadLocal存儲(chǔ)的當(dāng)前節(jié)點(diǎn),初始化值總是一個(gè)新建的CLHNode,locked狀態(tài)為false。
          ????CLHNode?currNode?=?curNode.get();
          ????//?此時(shí)把lock狀態(tài)置為true,表示一個(gè)有效狀態(tài),
          ????//?即獲取到了鎖或正在等待鎖的狀態(tài)
          ????currNode.locked?=?true;
          ????//?當(dāng)一個(gè)線程到來(lái)時(shí),總是將尾結(jié)點(diǎn)取出來(lái)賦值給當(dāng)前線程的前繼節(jié)點(diǎn);
          ????//?然后再把當(dāng)前線程的當(dāng)前節(jié)點(diǎn)賦值給尾節(jié)點(diǎn)
          ????//?【注意】在多線程并發(fā)情況下,這里通過(guò)AtomicReference類(lèi)能防止并發(fā)問(wèn)題
          ????//?【注意】哪個(gè)線程先執(zhí)行到這里就會(huì)先執(zhí)行predNode.set(preNode);語(yǔ)句,因此構(gòu)建了一條邏輯線程等待鏈
          ????//?這條鏈避免了線程饑餓現(xiàn)象發(fā)生
          ????CLHNode?preNode?=?tailNode.getAndSet(currNode);
          ????//?將剛獲取的尾結(jié)點(diǎn)(前一線程的當(dāng)前節(jié)點(diǎn))付給當(dāng)前線程的前繼節(jié)點(diǎn)ThreadLocal
          ????//?【思考】這句代碼也可以去掉嗎,如果去掉有影響嗎?
          ????predNode.set(preNode);
          ????//?【1】若前繼節(jié)點(diǎn)的locked狀態(tài)為false,則表示獲取到了鎖,不用自旋等待;
          ????//?【2】若前繼節(jié)點(diǎn)的locked狀態(tài)為true,則表示前一線程獲取到了鎖或者正在等待,自旋等待
          ????while?(preNode.locked)?{
          ????????try?{
          ????????????Thread.sleep(1000);
          ????????}?catch?(Exception?e)?{

          ????????}
          ????????System.out.println("線程"?+?Thread.currentThread().getName()?+?"沒(méi)能獲取到鎖,進(jìn)行自旋等待。。。");
          ????}
          ????//?能執(zhí)行到這里,說(shuō)明當(dāng)前線程獲取到了鎖
          ????System.out.println("線程"?+?Thread.currentThread().getName()?+?"獲取到了鎖?。?!");
          }

          雖然代碼的注釋已經(jīng)很詳細(xì),我們還是縷一縷線程加鎖的過(guò)程:

          1. 首先獲得當(dāng)前線程的當(dāng)前節(jié)點(diǎn)curNode,這里每次獲取的CLHNode節(jié)點(diǎn)的locked狀態(tài)都為false
          2. 然后將當(dāng)前CLHNode節(jié)點(diǎn)的locked狀態(tài)賦值為true,表示當(dāng)前線程的一種有效狀態(tài),即獲取到了鎖或正在等待鎖的狀態(tài);
          3. 因?yàn)槲仓羔?code style="box-sizing: border-box;font-family: 'Operator Mono', Consolas, Monaco, Menlo, monospace;font-size: 14px;overflow-wrap: break-word;padding: 2px 4px;border-radius: 4px;margin-right: 2px;margin-left: 2px;color: rgb(53, 179, 120);background-color: rgba(27, 31, 35, 0.05);word-break: break-all;">tailNode的總是指向了前一個(gè)線程的CLHNode節(jié)點(diǎn),因此這里利用尾指針tailNode取出前一個(gè)線程的CLHNode節(jié)點(diǎn),然后賦值給當(dāng)前線程的前繼節(jié)點(diǎn)predNode,并且將尾指針重新指向最后一個(gè)節(jié)點(diǎn)即當(dāng)前線程的當(dāng)前CLHNode節(jié)點(diǎn),以便下一個(gè)線程到來(lái)時(shí)使用;
          4. 根據(jù)前繼節(jié)點(diǎn)(前一個(gè)線程)的locked狀態(tài)判斷,若lockedfalse,則說(shuō)明前一個(gè)線程釋放了鎖,當(dāng)前線程即可獲得鎖,不用自旋等待;若前繼節(jié)點(diǎn)的locked狀態(tài)為true,則表示前一線程獲取到了鎖或者正在等待,自旋等待。

          為了更通俗易懂,我們用一個(gè)圖來(lái)說(shuō)明。

          **假如有這么一個(gè)場(chǎng)景:**有四個(gè)并發(fā)線程同時(shí)啟動(dòng)執(zhí)行l(wèi)ock操作,假如四個(gè)線程的實(shí)際執(zhí)行順序?yàn)椋簍hreadA<--threadB<--threadC<--threadD

          第一步,線程A過(guò)來(lái),執(zhí)行了lock操作,獲得了鎖,此時(shí)locked狀態(tài)為true,如下圖:



          第二步,線程B過(guò)來(lái),執(zhí)行了lock操作,由于線程A還未釋放鎖,此時(shí)自旋等待,locked狀態(tài)也為true,如下圖:第三步,線程C過(guò)來(lái),執(zhí)行了lock操作,由于線程B處于自旋等待,此時(shí)線程C也自旋等待(因此CLH鎖是公平鎖),locked狀態(tài)也為true,如下圖:第四步,線程D過(guò)來(lái),執(zhí)行了lock操作,由于線程C處于自旋等待,此時(shí)線程D也自旋等待,locked狀態(tài)也為true,如下圖:

          這就是多個(gè)線程并發(fā)加鎖的一個(gè)過(guò)程圖解,當(dāng)前線程只要判斷前一線程的locked狀態(tài)如果是true,那么則說(shuō)明前一線程要么拿到了鎖,要么也處于自旋等待狀態(tài),所以自己也要自旋等待。而尾指針tailNode總是指向最后一個(gè)線程的CLHNode節(jié)點(diǎn)。

          4.3 CLH鎖的釋放鎖過(guò)程

          前面用圖解結(jié)合代碼說(shuō)明了CLH鎖的加鎖過(guò)程,那么,CLH鎖的釋放鎖的過(guò)程又是怎樣的呢?同樣,我們先貼下釋放鎖的代碼:

          //?CLHLock.java

          /**
          ?*?釋放鎖
          ?*/

          public?void?unLock()?{
          ????//?獲取當(dāng)前線程的當(dāng)前節(jié)點(diǎn)
          ????CLHNode?node?=?curNode.get();
          ????//?進(jìn)行解鎖操作
          ????//?這里將locked至為false,此時(shí)執(zhí)行了lock方法正在自旋等待的后繼節(jié)點(diǎn)將會(huì)獲取到鎖
          ????//?【注意】而不是所有正在自旋等待的線程去并發(fā)競(jìng)爭(zhēng)鎖
          ????node.locked?=?false;
          ????System.out.println("線程"?+?Thread.currentThread().getName()?+?"釋放了鎖?。?!");
          ????//?小伙伴們可以思考下,下面兩句代碼的作用是什么???
          ????CLHNode?newCurNode?=?new?CLHNode();
          ????curNode.set(newCurNode);

          ????//?【優(yōu)化】能提高GC效率和節(jié)省內(nèi)存空間,請(qǐng)思考:這是為什么?
          ????//?curNode.set(predNode.get());
          }

          可以看到釋放CLH鎖的過(guò)程代碼比加鎖簡(jiǎn)單多了,下面同樣縷一縷:

          1. 首先從當(dāng)前線程的線程本地變量中獲取出當(dāng)前CLHNode節(jié)點(diǎn),同時(shí)這個(gè)CLHNode節(jié)點(diǎn)被后面一個(gè)線程的preNode變量指向著;
          2. 然后將locked狀態(tài)置為false即釋放了鎖;

          注意:locked因?yàn)楸?code style="box-sizing: border-box;font-family: 'Operator Mono', Consolas, Monaco, Menlo, monospace;font-size: 14px;overflow-wrap: break-word;padding: 2px 4px;border-radius: 4px;margin-right: 2px;margin-left: 2px;color: rgb(53, 179, 120);background-color: rgba(27, 31, 35, 0.05);word-break: break-all;">volitile關(guān)鍵字修飾,此時(shí)后面自旋等待的線程的局部變量preNode.locked也為false,因此后面自旋等待的線程結(jié)束while循環(huán)即結(jié)束自旋等待,此時(shí)也獲取到了鎖。這一步驟也在異步進(jìn)行著。

          1. 然后給當(dāng)前線程的表示當(dāng)前節(jié)點(diǎn)的線程本地變量重新賦值為一個(gè)新的CLHNode。

          思考:這一步看上去是多余的,其實(shí)并不是。請(qǐng)思考下為什么這么做?我們后續(xù)會(huì)繼續(xù)深入講解。

          我們還是用一個(gè)圖來(lái)說(shuō)說(shuō)明CLH鎖釋放鎖的場(chǎng)景,接著前面四個(gè)線程加鎖的場(chǎng)景,假如這四個(gè)線程加鎖后,線程A開(kāi)始釋放鎖,此時(shí)線程B獲取到鎖,結(jié)束自旋等待,然后線程C和線程D仍然自旋等待,如下圖:以此類(lèi)推,線程B釋放鎖的過(guò)程也跟上圖類(lèi)似,這里不再贅述。

          4.4 同個(gè)線程加鎖釋放鎖再次正常獲取鎖

          在前面4.3小節(jié)講到釋放鎖unLock方法中有下面兩句代碼:

          ??CLHNode?newCurNode?=?new?CLHNode();
          ??curNode.set(newCurNode);

          這兩句代碼的作用是什么?這里先直接說(shuō)結(jié)果:若沒(méi)有這兩句代碼,若同個(gè)線程加鎖釋放鎖后,然后再次執(zhí)行加鎖操作,這個(gè)線程就會(huì)陷入自旋等待的狀態(tài)。這是為啥,可能有些下伙伴也沒(méi)明白,勁越也是搞了蠻久才搞明白,嘿嘿。

          下面我們同樣通過(guò)一步一圖的形式來(lái)分析這兩句代碼的作用。假如有下面這樣一個(gè)場(chǎng)景:線程A獲取到了鎖,然后釋放鎖,然后再次獲取鎖。

          第一步:?線程A執(zhí)行了lock操作,獲取到了鎖,如下圖:

          上圖的加鎖操作中,線程A的當(dāng)前CLHNode節(jié)點(diǎn)的locked狀態(tài)被置為true;然后tailNode指針指向了當(dāng)前線程的當(dāng)前節(jié)點(diǎn);最后因?yàn)榍袄^節(jié)點(diǎn)的locked狀態(tài)為false,不用自旋等待,因此獲得了鎖。

          第二步:?線程A執(zhí)行了unLock操作,釋放了鎖,如下圖:

          上圖的釋放鎖操作中,線程A的當(dāng)前CLHNode節(jié)點(diǎn)的locked狀態(tài)被置為false,表示釋放了鎖;然后新建了一個(gè)新的CLHNode節(jié)點(diǎn)newCurNode,線程A的當(dāng)前節(jié)點(diǎn)線程本地變量值重新指向了newCurNode節(jié)點(diǎn)對(duì)象。

          第三步:?線程A再次執(zhí)行l(wèi)ock操作,重新獲得鎖,如下圖:



          上圖的再次獲取鎖操作中,首先將線程A的當(dāng)前CLHNode節(jié)點(diǎn)的locked狀態(tài)置為true;然后首先通過(guò)tailNode尾指針獲取到前繼節(jié)點(diǎn)即第一,二步中的curNode對(duì)象,然后線程A的前繼節(jié)點(diǎn)線程本地變量的值重新指向了重新指向了curNode對(duì)象;然后tailNode尾指針重新指向了新創(chuàng)建的CLHNode節(jié)點(diǎn)newCurNode對(duì)象。最后因?yàn)榍袄^節(jié)點(diǎn)的locked狀態(tài)為false,不用自旋等待,因此獲得了鎖。

          擴(kuò)展:?注意到以上圖片的preNode對(duì)象此時(shí)沒(méi)有任何引用,所以當(dāng)下一次會(huì)被GC掉。前面是通過(guò)每次執(zhí)行unLock操作都新建一個(gè)新的CLHNode節(jié)點(diǎn)對(duì)象newCurNode,然后讓線程A的當(dāng)前節(jié)點(diǎn)線程本地變量值重新指向newCurNode。因此這里完全不用重新創(chuàng)建新的CLHNode節(jié)點(diǎn)對(duì)象,可以通過(guò)curNode.set(predNode.get());這句代碼進(jìn)行優(yōu)化,提高GC效率和節(jié)省內(nèi)存空間。

          4.5 考慮同個(gè)線程加鎖釋放鎖再次獲取鎖異常的情況

          現(xiàn)在我們把unLock方法的CLHNode newCurNode = new CLHNode();curNode.set(newCurNode);這兩句代碼注釋掉,變成了下面這樣:

          //?CLHLock.java

          public?void?unLock()?{
          ????CLHNode?node?=?curNode.get();
          ????node.locked?=?false;
          ????System.out.println("線程"?+?Thread.currentThread().getName()?+?"釋放了鎖!??!");
          ????/*CLHNode?newCurNode?=?new?CLHNode();
          ????curNode.set(newCurNode);*/

          }

          那么結(jié)果就是線程A通過(guò)加鎖,釋放鎖后,再次獲取鎖時(shí)就會(huì)陷入自旋等待的狀態(tài),這又是為什么呢?我們下面來(lái)詳細(xì)分析。

          第一步:?線程A執(zhí)行了lock操作,獲取到了鎖,如下圖:

          上圖的加鎖操作中,線程A的當(dāng)前CLHNode節(jié)點(diǎn)的locked狀態(tài)被置為true;然后tailNode指針指向了當(dāng)前線程的當(dāng)前節(jié)點(diǎn);最后因?yàn)榍袄^節(jié)點(diǎn)的locked狀態(tài)為false,不用自旋等待,因此獲得了鎖。這一步?jīng)]有什么異常。

          第二步:?線程A執(zhí)行了unLock操作,釋放了鎖,如下圖:

          現(xiàn)在已經(jīng)把unLock方法的CLHNode newCurNode = new CLHNode();curNode.set(newCurNode);這兩句代碼注釋掉了,因此上圖的變化就是線程A的當(dāng)前CLHNode節(jié)點(diǎn)的locked狀態(tài)置為false即!可。

          第三步:?線程A再次執(zhí)行l(wèi)ock操作,此時(shí)會(huì)陷入一直自旋等待的狀態(tài),如下圖:通過(guò)上圖對(duì)線程A再次獲取鎖的lock方法的每一句代碼進(jìn)行分析,得知雖然第二步中將線程A的當(dāng)前CLHNodelocked狀態(tài)置為false了,但是在第三步線程A再次獲取鎖的過(guò)程中,將當(dāng)前CLHNodelocked狀態(tài)又置為true了,且尾指針tailNode指向的依然還是線程A的當(dāng)前當(dāng)前CLHNode節(jié)點(diǎn)。又因?yàn)槊看味际菍⑽仓羔?code style="box-sizing: border-box;font-family: 'Operator Mono', Consolas, Monaco, Menlo, monospace;font-size: 14px;overflow-wrap: break-word;padding: 2px 4px;border-radius: 4px;margin-right: 2px;margin-left: 2px;color: rgb(53, 179, 120);background-color: rgba(27, 31, 35, 0.05);word-break: break-all;">tailNode指向的CLHNode節(jié)點(diǎn)取出來(lái)給當(dāng)前線程的前繼CLHNode節(jié)點(diǎn),之后執(zhí)行while(predNode.locked) {}語(yǔ)句時(shí),此時(shí)因?yàn)?code style="box-sizing: border-box;font-family: 'Operator Mono', Consolas, Monaco, Menlo, monospace;font-size: 14px;overflow-wrap: break-word;padding: 2px 4px;border-radius: 4px;margin-right: 2px;margin-left: 2px;color: rgb(53, 179, 120);background-color: rgba(27, 31, 35, 0.05);word-break: break-all;">predNode.locked = true,因此線程A就永遠(yuǎn)自旋等待了。

          5 測(cè)試CLH鎖

          下面我們通過(guò)一個(gè)Demo來(lái)測(cè)試前面代碼實(shí)現(xiàn)的CLH鎖是否能正常工作,直接上測(cè)試代碼:

          //?CLHLockTest.java

          /**
          ?*?用來(lái)測(cè)試CLHLocke生不生效
          ?*
          ?*?定義一個(gè)靜態(tài)成員變量cnt,然后開(kāi)10個(gè)線程跑起來(lái),看能是否會(huì)有線程安全問(wèn)題
          ?*/

          public?class?CLHLockTest?{
          ????private?static?int?cnt?=?0;

          ????public?static?void?main(String[]?args)?throws?Exception?{
          ????????final?CLHLock?lock?=?new?CLHLock();

          ????????for?(int?i?=?0;?i?100;?i++)?{
          ????????????new?Thread(()?->?{
          ????????????????lock.lock();

          ????????????????cnt++;

          ????????????????lock.unLock();
          ????????????}).start();
          ????????}
          ????????//?讓main線程休眠10秒,確保其他線程全部執(zhí)行完
          ????????Thread.sleep(10000);
          ????????System.out.println();
          ????????System.out.println("cnt----------->>>"?+?cnt);

          ????}
          }

          下面附運(yùn)行結(jié)果截圖:

          PS:?這里為了截圖全面,因此只開(kāi)了10個(gè)線程。經(jīng)過(guò)勁越測(cè)試,開(kāi)100個(gè)線程,1000個(gè)線程也不會(huì)存在線程安全問(wèn)題。

          6 小結(jié)

          好了,前面我們通過(guò)多圖詳細(xì)說(shuō)明了CLH鎖的原理與實(shí)現(xiàn),那么我們?cè)賹?duì)前面的知識(shí)進(jìn)行一次小結(jié):

          1. 首先我們學(xué)習(xí)了自旋鎖和互斥鎖的概念與區(qū)別;
          2. 然后我們學(xué)習(xí)了什么是CLH鎖以及為什么要學(xué)習(xí)CLH鎖;
          3. 最后我們通過(guò)圖示+代碼實(shí)現(xiàn)的方式來(lái)學(xué)習(xí)CLH鎖的原理,從而為學(xué)習(xí)后面的AQS打好堅(jiān)實(shí)的基礎(chǔ)。

          原創(chuàng)不易,燃燒秀發(fā)輸出內(nèi)容,如果有一丟丟收獲,點(diǎn)個(gè)贊鼓勵(lì)一下吧!

          理了幾百本各類(lèi)技術(shù)電子書(shū),送給小伙伴們。關(guān)注公號(hào)回復(fù)【666】自行領(lǐng)取。和一些小伙伴們建了一個(gè)技術(shù)交流群,一起探討技術(shù)、分享技術(shù)資料,旨在共同學(xué)習(xí)進(jìn)步,如果感興趣就加入我們吧!


          關(guān)注,邁開(kāi)成長(zhǎng)的第一

          瀏覽 90
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          評(píng)論
          圖片
          表情
          推薦
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  狼友在线视频观看 | 天天夜夜狠狠 | 又黑又长的大黑鸡巴免费高清 | 国内一级黄色片 | 高清无码在线观看视频 |