整合營銷服務(wù)商

          電腦端+手機(jī)端+微信端=數(shù)據(jù)同步管理

          免費(fèi)咨詢熱線:

          自學(xué)并發(fā)編程之:Lock與Condition

          自學(xué)并發(fā)編程之:Lock與Condition

          ock與Condition

          8.1 互斥鎖

          8.1.1 鎖的可重入性

          “可重入鎖”是指當(dāng)一個線程調(diào)用 object.lock()獲取到鎖,進(jìn)入臨界區(qū)后,再次調(diào)用object.lock(),仍然可以獲取到該鎖。顯然,通常的鎖都要設(shè)計成可重入的,否則就會發(fā)生死鎖。

          synchronized關(guān)鍵字,就是可重入鎖。在一個synchronized方法method1()里面調(diào)用另外一個synchronized方法method2()。如果synchronized關(guān)鍵字不可重入,那么在method2()處就會發(fā)生阻塞,這顯然不可行。

          8.1.2 類繼承層次

          在正式介紹鎖的實(shí)現(xiàn)原理之前,先看一下 Concurrent 包中的與互斥鎖(ReentrantLock)相關(guān)類之間的繼承層次,如下圖所示:

          Lock是一個接口,其定義如下:

          常用的方法是lock()/unlock()。lock()不能被中斷,對應(yīng)的lockInterruptibly()可以被中斷。

          ReentrantLock本身沒有代碼邏輯,實(shí)現(xiàn)都在其內(nèi)部類Sync中:

          8.1.3 鎖的公平性VS非公平性

          Sync是一個抽象類,它有兩個子類FairSync與NonfairSync,分別對應(yīng)公平鎖和非公平鎖。從下面的ReentrantLock構(gòu)造方法可以看出,會傳入一個布爾類型的變量fair指定鎖是公平的還是非公平的,默認(rèn)為非公平的。

          什么叫公平鎖和非公平鎖呢?先舉個現(xiàn)實(shí)生活中的例子,一個人去火車站售票窗口買票,發(fā)現(xiàn)現(xiàn)場有人排隊,于是他排在隊伍末尾,遵循先到者優(yōu)先服務(wù)的規(guī)則,這叫公平;如果他去了不排隊,直接沖到窗口買票,這叫作不公平。

          對應(yīng)到鎖的例子,一個新的線程來了之后,看到有很多線程在排隊,自己排到隊伍末尾,這叫公平;線程來了之后直接去搶鎖,這叫作不公平。默認(rèn)設(shè)置的是非公平鎖,其實(shí)是為了提高效率,減少線程切換。

          鎖實(shí)現(xiàn)的基本原理

          Sync的父類AbstractQueuedSynchronizer經(jīng)常被稱作隊列同步器(AQS),這個類非常重要,該類的父類是AbstractOwnableSynchronizer。

          此處的鎖具備synchronized功能,即可以阻塞一個線程。為了實(shí)現(xiàn)一把具有阻塞或喚醒功能的鎖,需要幾個核心要素:

          1. 需要一個state變量,標(biāo)記該鎖的狀態(tài)。state變量至少有兩個值:0、1。對state變量的操作,使用CAS保證線程安全。
          2. 需要記錄當(dāng)前是哪個線程持有鎖。
          3. 需要底層支持對一個線程進(jìn)行阻塞喚醒操作。
          4. 需要有一個隊列維護(hù)所有阻塞的線程。這個隊列也必須是線程安全的無鎖隊列,也需要使用CAS。

          針對要素1和2,在上面兩個類中有對應(yīng)的體現(xiàn):

          state取值不僅可以是0、1,還可以大于1,就是為了支持鎖的可重入性。例如,同樣一個線程,調(diào)用5次lock,state會變成5;然后調(diào)用5次unlock,state減為0。

          當(dāng)state=0時,沒有線程持有鎖,exclusiveOwnerThread=null;

          當(dāng)state=1時,有一個線程持有鎖,exclusiveOwnerThread=該線程;

          當(dāng)state > 1時,說明該線程重入了該鎖。

          對于要素3,Unsafe類提供了阻塞或喚醒線程的一對操作原語,也就是park/unpark。

          有一個LockSupport的工具類,對這一對原語做了簡單封裝:

          在當(dāng)前線程中調(diào)用park(),該線程就會被阻塞;在另外一個線程中,調(diào)用unpark(Thread thread),傳入一個被阻塞的線程,就可以喚醒阻塞在park()地方的線程。

          unpark(Thread thread),它實(shí)現(xiàn)了一個線程對另外一個線程的“精準(zhǔn)喚醒”。notify也只是喚醒某一個線程,但無法指定具體喚醒哪個線程。

          針對要素4,在AQS中利用雙向鏈表和CAS實(shí)現(xiàn)了一個阻塞隊列。如下所示:

          阻塞隊列是整個AQS核心中的核心。如下圖所示,head指向雙向鏈表頭部,tail指向雙向鏈表尾部。入隊就是把新的Node加到tail后面,然后對tail進(jìn)行CAS操作;出隊就是對head進(jìn)行CAS操作,把head向后移一個位置。

          初始的時候,head=tail=NULL;然后,在往隊列中加入阻塞的線程時,會新建一個空的Node,讓head和tail都指向這個空Node;之后,在后面加入被阻塞的線程對象。所以,當(dāng)head=tail的時候,說明隊列為空。

          8.1.4 公平與非公平的lock()實(shí)現(xiàn)差異

          下面分析基于AQS,ReentrantLock在公平性和非公平性上的實(shí)現(xiàn)差異。

          8.1.5 阻塞隊列與喚醒機(jī)制

          下面進(jìn)入鎖的最為關(guān)鍵的部分,即acquireQueued(...)方法內(nèi)部一探究竟。

          先說addWaiter(...)方法,就是為當(dāng)前線程生成一個Node,然后把Node放入雙向鏈表的尾部。要注意的是,這只是把Thread對象放入了一個隊列中而已,線程本身并未阻塞。

          創(chuàng)建節(jié)點(diǎn),嘗試將節(jié)點(diǎn)追加到隊列尾部。獲取tail節(jié)點(diǎn),將tail節(jié)點(diǎn)的next設(shè)置為當(dāng)前節(jié)點(diǎn)。

          如果tail不存在,就初始化隊列。

          在addWaiter(...)方法把Thread對象加入阻塞隊列之后的工作就要靠acquireQueued(...)方法完成。線程一旦進(jìn)入acquireQueued(...)就會被無限期阻塞,即使有其他線程調(diào)用interrupt()方法也不能將其喚醒,除非有其他線程釋放了鎖,并且該線程拿到了鎖,才會從accquireQueued(...)返回。

          進(jìn)入acquireQueued(...),該線程被阻塞。在該方法返回的一刻,就是拿到鎖的那一刻,也就是被喚醒的那一刻,此時會刪除隊列的第一個元素(head指針前移1個節(jié)點(diǎn))。

          首先,acquireQueued(...)方法有一個返回值,表示什么意思呢?雖然該方法不會中斷響應(yīng),但它會記錄被阻塞期間有沒有其他線程向它發(fā)送過中斷信號。如果有,則該方法會返回true;否則,返回false。

          基于這個返回值,才有了下面的代碼:

          當(dāng) acquireQueued(...)返回 true 時,會調(diào)用 selfInterrupt(),自己給自己發(fā)送中斷信號,也就是自己把自己的中斷標(biāo)志位設(shè)為true。之所以要這么做,是因?yàn)樽约涸谧枞陂g,收到其他線程中斷信號沒有及時響應(yīng),現(xiàn)在要進(jìn)行補(bǔ)償。這樣一來,如果該線程在lock代碼塊內(nèi)部有調(diào)用sleep()之類的阻塞方法,就可以拋出異常,響應(yīng)該中斷信號。

          阻塞就發(fā)生在下面這個方法中:

          線程調(diào)用 park()方法,自己把自己阻塞起來,直到被其他線程喚醒,該方法返回。

          park()方法返回有兩種情況。

          1. 其他線程調(diào)用了unpark(Thread t)。
          2. 其他線程調(diào)用了t.interrupt()。這里要注意的是,lock()不能響應(yīng)中斷,但LockSupport.park()會響應(yīng)中斷。

          也正因?yàn)長ockSupport.park()可能被中斷喚醒,acquireQueued(...)方法才寫了一個for死循環(huán)。喚醒之后,如果發(fā)現(xiàn)自己排在隊列頭部,就去拿鎖;如果拿不到鎖,則再次自己阻塞自己。不斷重復(fù)此過程,直到拿到鎖。

          被喚醒之后,通過Thread.interrupted()來判斷是否被中斷喚醒。如果是情況1,會返回false;如果是情況2,則返回true。

          8.1.6 unlock()實(shí)現(xiàn)分析

          說完了lock,下面分析unlock的實(shí)現(xiàn)。unlock不區(qū)分公平還是非公平。

          上圖中,當(dāng)前線程要釋放鎖,先調(diào)用tryRelease(arg)方法,如果返回true,則取出head,讓head獲取鎖。

          對于tryRelease方法:

          首先計算當(dāng)前線程釋放鎖后的state值。

          如果當(dāng)前線程不是排他線程,則拋異常,因?yàn)橹挥蝎@取鎖的線程才可以進(jìn)行釋放鎖的操作。

          此時設(shè)置state,沒有使用CAS,因?yàn)槭菃尉€程操作。

          再看unparkSuccessor方法:

          release()里面做了兩件事:tryRelease(...)方法釋放鎖;unparkSuccessor(...)方法喚醒隊列中的后繼者。

          8.1.7 lockInterruptibly()實(shí)現(xiàn)分析

          上面的 lock 不能被中斷,這里的 lockInterruptibly()可以被中斷:

          這里的 acquireInterruptibly(...)也是 AQS 的模板方法,里面的 tryAcquire(...)分別被 FairSync和NonfairSync實(shí)現(xiàn)。

          主要看doAcquireInterruptibly(...)方法:

          當(dāng)parkAndCheckInterrupt()返回true的時候,說明有其他線程發(fā)送中斷信號,直接拋出InterruptedException,跳出for循環(huán),整個方法返回。

          8.1.8 tryLock()實(shí)現(xiàn)分析

          tryLock()實(shí)現(xiàn)基于調(diào)用非公平鎖的tryAcquire(...),對state進(jìn)行CAS操作,如果操作成功就拿到鎖;

          如果操作不成功則直接返回false,也不阻塞。


          8.2 讀寫鎖

          和互斥鎖相比,讀寫鎖(ReentrantReadWriteLock)就是讀線程和讀線程之間不互斥。

          讀讀不互斥,讀寫互斥,寫寫互斥

          8.2.1 類繼承層次

          ReadWriteLock是一個接口,內(nèi)部由兩個Lock接口組成。

          ReentrantReadWriteLock實(shí)現(xiàn)了該接口,使用方式如下:

          也就是說,當(dāng)使用 ReadWriteLock 的時候,并不是直接使用,而是獲得其內(nèi)部的讀鎖和寫鎖,然后分別調(diào)用lock/unlock。

          8.2.2 讀寫鎖實(shí)現(xiàn)的基本原理

          個視圖呢?可以理解為是一把鎖,線程分成兩類:讀線程和寫線程。讀線程和寫線程之間不互斥(可以同時拿到這把鎖),讀線程之間不互斥,寫線程之間互斥。

          從下面的構(gòu)造方法也可以看出,readerLock和writerLock實(shí)際共用同一個sync對象。sync對象同互斥鎖一樣,分為非公平和公平兩種策略,并繼承自AQS。

          同互斥鎖一樣,讀寫鎖也是用state變量來表示鎖狀態(tài)的。只是state變量在這里的含義和互斥鎖完全不同。在內(nèi)部類Sync中,對state變量進(jìn)行了重新定義,如下所示:

          也就是把 state 變量拆成兩半,低16位,用來記錄寫鎖。但同一時間既然只能有一個線程寫,為什么還需要16位呢?這是因?yàn)橐粋€寫線程可能多次重入。例如,低16位的值等于5,表示一個寫線程重入了5次。

          高16位,用來“讀”鎖。例如,高16位的值等于5,既可以表示5個讀線程都拿到了該鎖;也可以表示一個讀線程重入了5次。

          為什么要把一個int類型變量拆成兩半,而不是用兩個int型變量分別表示讀鎖和寫鎖的狀態(tài)呢?

          這是因?yàn)闊o法用一次CAS 同時操作兩個int變量,所以用了一個int型的高16位和低16位分別表示讀鎖和寫鎖的狀態(tài)。

          當(dāng)state=0時,說明既沒有線程持有讀鎖,也沒有線程持有寫鎖;當(dāng)state !=0時,要么有線程持有讀鎖,要么有線程持有寫鎖,兩者不能同時成立,因?yàn)樽x和寫互斥。這時再進(jìn)一步通過sharedCount(state)和exclusiveCount(state)判斷到底是讀線程還是寫線程持有了該鎖。

          8.2.3 AQS的兩對模板方法

          下面介紹在ReentrantReadWriteLock的兩個內(nèi)部類ReadLock和WriteLock中,是如何使用state變量的。

          acquire/release、acquireShared/releaseShared 是AQS里面的兩對模板方法。互斥鎖和讀寫鎖的寫鎖都是基于acquire/release模板方法來實(shí)現(xiàn)的。讀寫鎖的讀鎖是基于acquireShared/releaseShared這對模板方法來實(shí)現(xiàn)的。這兩對模板方法的代碼如下:

          將讀/寫、公平/非公平進(jìn)行排列組合,就有4種組合。如下圖所示,上面的兩個方法都是在Sync中實(shí)現(xiàn)的。Sync中的兩個方法又是模板方法,在NonfairSync和FairSync中分別有實(shí)現(xiàn)。最終的對應(yīng)關(guān)系如下:

          1. 讀鎖的公平實(shí)現(xiàn):Sync.tryAccquireShared()+FairSync中的兩個重寫的子方法。
          2. 讀鎖的非公平實(shí)現(xiàn):Sync.tryAccquireShared()+NonfairSync中的兩個重寫的子方法。
          3. 寫鎖的公平實(shí)現(xiàn):Sync.tryAccquire()+FairSync中的兩個重寫的子方法。
          4. 寫鎖的非公平實(shí)現(xiàn):Sync.tryAccquire()+NonfairSync中的兩個重寫的子方法。

          對于公平,比較容易理解,不論是讀鎖,還是寫鎖,只要隊列中有其他線程在排隊(排隊等讀鎖,或者排隊等寫鎖),就不能直接去搶鎖,要排在隊列尾部。

          對于非公平,讀鎖和寫鎖的實(shí)現(xiàn)策略略有差異。

          寫線程能搶鎖,前提是state=0,只有在沒有其他線程持有讀鎖或?qū)戞i的情況下,它才有機(jī)會去搶鎖。或者state !=0,但那個持有寫鎖的線程是它自己,再次重入。寫線程是非公平的,即writerShouldBlock()方法一直返回false。

          對于讀線程,假設(shè)當(dāng)前線程被讀線程持有,然后其他讀線程還非公平地一直去搶,可能導(dǎo)致寫線程永遠(yuǎn)拿不到鎖,所以對于讀線程的非公平,要做一些“約束”。當(dāng)發(fā)現(xiàn)隊列的第1個元素是寫線程的時候,讀線程也要阻塞,不能直接去搶。即偏向?qū)懢€程。

          8.2.4 WriteLock公平vs非公平實(shí)現(xiàn)

          寫鎖是排他鎖,實(shí)現(xiàn)策略類似于互斥鎖。

          1.tryLock()實(shí)現(xiàn)分析

          lock()方法:

          在互斥鎖部分講過了。tryLock和lock方法不區(qū)分公平/非公平。

          2.unlock()實(shí)現(xiàn)分析

          unlock()方法不區(qū)分公平/非公平。

          8.2.5 ReadLock公平vs非公平實(shí)現(xiàn)

          讀鎖是共享鎖,其實(shí)現(xiàn)策略和排他鎖有很大的差異。

          1.tryLock()實(shí)現(xiàn)分析

          2.unlock()實(shí)現(xiàn)分析

          tryReleaseShared()的實(shí)現(xiàn):

          因?yàn)樽x鎖是共享鎖,多個線程會同時持有讀鎖,所以對讀鎖的釋放不能直接減1,而是需要通過一個for循環(huán)+CAS操作不斷重試。這是tryReleaseShared和tryRelease的根本差異所在。


          8.3 Condition

          8.3.1 Condition與Lock的關(guān)系

          Condition本身也是一個接口,其功能和wait/notify類似,如下所示:

          wait()/notify()必須和synchronized一起使用,Condition也必須和Lock一起使用。因此,在Lock的接口中,有一個與Condition相關(guān)的接口:

          8.3.2 Condition的使用場景

          以ArrayBlockingQueue為例。如下所示為一個用數(shù)組實(shí)現(xiàn)的阻塞隊列,執(zhí)行put(...)操作的時候,隊列滿了,生產(chǎn)者線程被阻塞;執(zhí)行take()操作的時候,隊列為空,消費(fèi)者線程被阻塞。

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

          可以發(fā)現(xiàn),Condition的使用很方便,避免了wait/notify的生產(chǎn)者通知生產(chǎn)者、消費(fèi)者通知消費(fèi)者的問題。具體實(shí)現(xiàn)如下:

          由于Condition必須和Lock一起使用,所以Condition的實(shí)現(xiàn)也是Lock的一部分。首先查看互斥鎖和讀寫鎖中Condition的構(gòu)造方法:

          首先,讀寫鎖中的 ReadLock 是不支持 Condition 的,讀寫鎖的寫鎖和互斥鎖都支持Condition。雖然它們各自調(diào)用的是自己的內(nèi)部類Sync,但內(nèi)部類Sync都繼承自AQS。因此,上面的代碼sync.newCondition最終都調(diào)用了AQS中的newCondition:

          每一個Condition對象上面,都阻塞了多個線程。因此,在ConditionObject內(nèi)部也有一個雙向鏈表組成的隊列,如下所示:

          下面來看一下在await()/notify()方法中,是如何使用這個隊列的。

          8.3.4 await()實(shí)現(xiàn)分析

          關(guān)于await,有幾個關(guān)鍵點(diǎn)要說明:

          1. 線程調(diào)用 await()的時候,肯定已經(jīng)先拿到了鎖。所以,在 addConditionWaiter()內(nèi)部,對這個雙向鏈表的操作不需要執(zhí)行CAS操作,線程天生是安全的,代碼如下:
          2. 在線程執(zhí)行wait操作之前,必須先釋放鎖。也就是fullyRelease(node),否則會發(fā)生死鎖。這個和wait/notify與synchronized的配合機(jī)制一樣。
          3. 線程從wait中被喚醒后,必須用acquireQueued(node, savedState)方法重新拿鎖。
          4. checkInterruptWhileWaiting(node)代碼在park(this)代碼之后,是為了檢測在park期間是否收到過中斷信號。當(dāng)線程從park中醒來時,有兩種可能:一種是其他線程調(diào)用了unpark,另一種是收到中斷信號。這里的await()方法是可以響應(yīng)中斷的,所以當(dāng)發(fā)現(xiàn)自己被中斷喚醒的,而不是被unpark喚醒的時,會直接退出while循環(huán),await()方法也會返回。
          5. isOnSyncQueue(node)用于判斷該Node是否在AQS的同步隊列里面。初始的時候,Node只 在Condition的隊列里,而不在AQS的隊列里。但執(zhí)行notity操作的時候,會放進(jìn)AQS的同步隊列。

          8.3.5 awaitUninterruptibly()實(shí)現(xiàn)分析

          與await()不同,awaitUninterruptibly()不會響應(yīng)中斷,其方法的定義中不會有中斷異常拋出,下面分析其實(shí)現(xiàn)和await()的區(qū)別。

          可以看出,整體代碼和 await()類似,區(qū)別在于收到異常后,不會拋出異常,而是繼續(xù)執(zhí)行while循環(huán)。

          8.3.6 notify()實(shí)現(xiàn)分析

          同 await()一樣,在調(diào)用 notify()的時候,必須先拿到鎖(否則就會拋出上面的異常),是因?yàn)榍懊鎴?zhí)行await()的時候,把鎖釋放了。

          然后,從隊列中取出firstWaiter,喚醒它。在通過調(diào)用unpark喚醒它之前,先用enq(node)方法把這個Node放入AQS的鎖對應(yīng)的阻塞隊列中。也正因?yàn)槿绱耍庞辛薬wait()方法里面的判斷條件:

          while( ! isOnSyncQueue(node))

          這個判斷條件滿足,說明await線程不是被中斷,而是被unpark喚醒的。

          notifyAll()與此類似。


          8.4 StampedLock

          8.4.1 為什么引入StampedLock

          StampedLock是在JDK8中新增的,有了讀寫鎖,為什么還要引入StampedLock呢?

          可以看到,從ReentrantLock到StampedLock,并發(fā)度依次提高。

          另一方面,因?yàn)镽eentrantReadWriteLock采用的是“悲觀讀”的策略,當(dāng)?shù)谝粋€讀線程拿到鎖之后,第二個、第三個讀線程還可以拿到鎖,使得寫線程一直拿不到鎖,可能導(dǎo)致寫線程“餓死”。雖然在其公平或非公平的實(shí)現(xiàn)中,都盡量避免這種情形,但還有可能發(fā)生。

          StampedLock引入了“樂觀讀”策略,讀的時候不加讀鎖,讀出來發(fā)現(xiàn)數(shù)據(jù)被修改了,再升級為“悲觀讀”,相當(dāng)于降低了“讀”的地位,把搶鎖的天平往“寫”的一方傾斜了一下,避免寫線程被餓死。

          8.4.2 使用場景

          在剖析其原理之前,下面先以官方的一個例子來看一下StampedLock如何使用。

          如上面代碼所示,有一個Point類,多個線程調(diào)用move()方法,修改坐標(biāo);還有多個線程調(diào)用distanceFromOrigin()方法,求距離。

          首先,執(zhí)行move操作的時候,要加寫鎖。這個用法和ReadWriteLock的用法沒有區(qū)別,寫操作和寫操作也是互斥的。

          關(guān)鍵在于讀的時候,用了一個“樂觀讀”sl.tryOptimisticRead(),相當(dāng)于在讀之前給數(shù)據(jù)的狀態(tài)做了一個“快照”。然后,把數(shù)據(jù)拷貝到內(nèi)存里面,在用之前,再比對一次版本號。如果版本號變了,則說明在讀的期間有其他線程修改了數(shù)據(jù)。讀出來的數(shù)據(jù)廢棄,重新獲取讀鎖。關(guān)鍵代碼就是下面這三行:

          要說明的是,這三行關(guān)鍵代碼對順序非常敏感,不能有重排序。因?yàn)?state 變量已經(jīng)是volatile,所以可以禁止重排序,但stamp并不是volatile的。為此,在validate(stamp)方法里面插入內(nèi)存屏障。

          8.4.3 "樂觀讀"的實(shí)現(xiàn)原理

          首先,StampedLock是一個讀寫鎖,因此也會像讀寫鎖那樣,把一個state變量分成兩半,分別表示讀鎖和寫鎖的狀態(tài)。同時,它還需要一個數(shù)據(jù)的version。但是,一次CAS沒有辦法操作兩個變量,所以這個state變量本身同時也表示了數(shù)據(jù)的version。下面先分析state變量。

          如下圖:用最低的8位表示讀和寫的狀態(tài),其中第8位表示寫鎖的狀態(tài),最低的7位表示讀鎖的狀態(tài)。因?yàn)閷戞i只有一個bit位,所以寫鎖是不可重入的。

          初始值不為0,而是把WBIT 向左移動了一位,也就是上面的ORIGIN 常量,構(gòu)造方法如下所示。

          為什么state的初始值不設(shè)為0呢?看樂觀鎖的實(shí)現(xiàn):

          上面兩個方法必須結(jié)合起來看:當(dāng)state&WBIT !=0的時候,說明有線程持有寫鎖,上面的tryOptimisticRead會永遠(yuǎn)返回0。這樣,再調(diào)用validate(stamp),也就是validate(0)也會永遠(yuǎn)返回false。這正是我們想要的邏輯:當(dāng)有線程持有寫鎖的時候,validate永遠(yuǎn)返回false,無論寫線程是否釋放了寫鎖。因?yàn)闊o論是否釋放了(state回到初始值)寫鎖,state值都不為0,所以validate(0)永遠(yuǎn)為false。

          為什么上面的validate(...)方法不直接比較stamp=state,而要比較state&SBITS=state&SBITS 呢?

          因?yàn)樽x鎖和讀鎖是不互斥的!

          所以,即使在“樂觀讀”的時候,state 值被修改了,但如果它改的是第7位,validate(...)還是會返回true。

          另外要說明的一點(diǎn)是,上面使用了內(nèi)存屏障VarHandle.acquireFence();,是因?yàn)樵谶@行代碼的下一行里面的stamp、SBITS變量不是volatile的,由此可以禁止其和前面的currentX=X,currentY=Y進(jìn)行重排序。

          通過上面的分析,可以發(fā)現(xiàn)state的設(shè)計非常巧妙。只通過一個變量,既實(shí)現(xiàn)了讀鎖、寫鎖的狀態(tài)記錄,還實(shí)現(xiàn)了數(shù)據(jù)的版本號的記錄。

          8.4.4 悲觀讀/寫:“阻塞”與“自旋”策略實(shí)現(xiàn)差異

          同ReadWriteLock一樣,StampedLock也要進(jìn)行悲觀的讀鎖和寫鎖操作。不過,它不是基于AQS實(shí)現(xiàn)的,而是內(nèi)部重新實(shí)現(xiàn)了一個阻塞隊列。如下所示。

          這個阻塞隊列和 AQS 里面的很像。

          剛開始的時候,whead=wtail=NULL,然后初始化,建一個空節(jié)點(diǎn),whead和wtail都指向這個空節(jié)點(diǎn),之后往里面加入一個個讀線程或?qū)懢€程節(jié)點(diǎn)。

          但基于這個阻塞隊列實(shí)現(xiàn)的鎖的調(diào)度策略和AQS很不一樣,也就是“自旋”。

          在AQS里面,當(dāng)一個線程CAS state失敗之后,會立即加入阻塞隊列,并且進(jìn)入阻塞狀態(tài)。

          但在StampedLock中,CAS state失敗之后,會不斷自旋,自旋足夠多的次數(shù)之后,如果還拿不到鎖,才進(jìn)入阻塞狀態(tài)。

          為此,根據(jù)CPU的核數(shù),定義了自旋次數(shù)的常量值。如果是單核的CPU,肯定不能自旋,在多核情況下,才采用自旋策略。

          下面以寫鎖的加鎖,也就是StampedLock的writeLock()方法為例,來看一下自旋的實(shí)現(xiàn)。

          如上面代碼所示,當(dāng)state&ABITS==0的時候,說明既沒有線程持有讀鎖,也沒有線程持有寫鎖,此時當(dāng)前線程才有資格通過CAS操作state。若操作不成功,則調(diào)用acquireWrite()方法進(jìn)入阻塞隊列,并進(jìn)行自旋,這個方法是整個加鎖操作的核心,代碼如下:

          整個acquireWrite(...)方法是兩個大的for循環(huán),內(nèi)部實(shí)現(xiàn)了非常復(fù)雜的自旋策略。在第一個大的for循環(huán)里面,目的就是把該Node加入隊列的尾部,一邊加入,一邊通過CAS操作嘗試獲得鎖。如果獲得了,整個方法就會返回;如果不能獲得鎖,會一直自旋,直到加入隊列尾部。

          在第二個大的for循環(huán)里,也就是該Node已經(jīng)在隊列尾部了。這個時候,如果發(fā)現(xiàn)自己剛好也在隊列頭部,說明隊列中除了空的Head節(jié)點(diǎn),就是當(dāng)前線程了。此時,再進(jìn)行新一輪的自旋,直到達(dá)到MAX_HEAD_SPINS次數(shù),然后進(jìn)入阻塞。這里有一個關(guān)鍵點(diǎn)要說明:當(dāng)release(...)方法被調(diào)用之后,會喚醒隊列頭部的第1個元素,此時會執(zhí)行第二個大的for循環(huán)里面的邏輯,也就是接著for循環(huán)里面park()方法后面的代碼往下執(zhí)行。

          另外一個不同于AQS的阻塞隊列的地方是,在每個WNode里面有一個cowait指針,用于串聯(lián)起所有的讀線程。例如,隊列尾部阻塞的是一個讀線程 1,現(xiàn)在又來了讀線程 2、3,那么會通過cowait指針,把1、2、3串聯(lián)起來。1被喚醒之后,2、3也隨之一起被喚醒,因?yàn)樽x和讀之間不互斥。

          明白加鎖的自旋策略后,下面來看鎖的釋放操作。和讀寫鎖的實(shí)現(xiàn)類似,也是做了兩件事情:一是把state變量置回原位,二是喚醒阻塞隊列中的第一個節(jié)點(diǎn)。

          原文鏈接:https://www.cnblogs.com/yangchen-geek/p/15489631.html

          篇文章主要介紹了Python線程條件變量Condition原理解析,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友可以參考下 Condition 對象就是條件變量,它總是與某種鎖相關(guān)聯(lián),可以是外部傳入的鎖或是系統(tǒng)默認(rèn)創(chuàng)建的鎖。當(dāng)幾個條件變量共享一個鎖時,你就應(yīng)該自己傳入一個鎖。這個鎖不需要你操心,Condition 類會管理它。 acquire() 和 release() 可以操控這個相關(guān)聯(lián)的鎖。其他的方法都必須在這個鎖被鎖上的情況下使用。wait() 會釋放這個鎖,阻塞本線程直到其他線程通過 notify() 或 notify_all() 來喚醒它。一旦被喚醒,這個鎖又被 wait() 鎖上。 經(jīng)典的 consumer/producer 問題的代碼示例為:

          import threading
          import time
          import logging
          
          logging.basicConfig(level=logging.DEBUG,
          format='(%(threadName)-9s) %(message)s',)
          
          def consumer(cv):
          logging.debug('Consumer thread started ...')
          with cv:
          logging.debug('Consumer waiting ...')
          cv.acquire()
          cv.wait()
          logging.debug('Consumer consumed the resource')
          cv.release()
          
          def producer(cv):
          logging.debug('Producer thread started ...')
          with cv:
          cv.acquire()
          logging.debug('Making resource available')
          logging.debug('Notifying to all consumers')
          cv.notify()
          cv.release()
          
          if __name__=='__main__':
          condition=threading.Condition()
          cs1=threading.Thread(name='consumer1', target=consumer, args=(condition,))
          #cs2=threading.Thread(name='consumer2', target=consumer, args=(condition,state))
          pd=threading.Thread(name='producer', target=producer, args=(condition,))
          
          cs1.start()
          time.sleep(2)
          #cs2.start()
          #time.sleep(2)
          pd.start()

          以上就是本文的全部內(nèi)容,希望對大家的學(xué)習(xí)有所幫助

          轉(zhuǎn)自:https://www.linuxprobe.com/python-condition-parsing.html

          們可以使用以下的方式去渲染html

          func main() {
          	router :=gin.Default()
          	router.LoadHTMLGlob("templates/*")
          	//router.LoadHTMLFiles("templates/template1.html", "templates/template2.html")
          	router.GET("/index", func(c *gin.Context) {
          		c.HTML(http.StatusOK, "index.tmpl", gin.H{
          			"title": "Main website",
          		})
          	})
          	router.Run(":8080")
          }

          在html中我們可以使用特殊的雙花括號來渲染title這個值

          <html>
          	<h1>
          		{{ .title }}
          	</h1>
          </html>
          

          值得注意的是這種方式并不是gin特有的,而是golang特有的,它還有其他的模板語法。


          模板語法:

          定義變量:

          {{$article :="hello"}}

          也可以給變量賦值

          {{$article :=.ArticleContent}}

          函數(shù)的調(diào)用:

          {{funcname .arg1 .arg2}}

          判斷語法:

          {{if .condition}}
          {{end}}
          {{if .condition1}}
          {{else if .contition2}}
          {{end}}
          • not 非
          {{if not .condition}}
          {{end}}
          • and 與
          {{if and .condition1 .condition2}}
          {{end}}
          • or 或
          {{if or .condition1 .condition2}}
          {{end}}
          • eq 等于
          {{if eq .var1 .var2}}
          {{end}}
          • ne 不等于
          {{if ne .var1 .var2}}
          {{end}}
          • lt 小于
          (less than){{if lt .var1 .var2}}
          {{end}}
          • le 小于等于
          {{if le .var1 .var2}}
          {{end}}
          • gt 大于
          {{if gt .var1 .var2}}
          {{end}}
          • ge 大于等于
          {{if ge .var1 .var2}}
          {{end}}

          循環(huán):

          {{range $i, $v :=.slice}}
          {{end}}

          引入一個模板:

          {{template "navbar"}}

          主站蜘蛛池模板: 日本v片免费一区二区三区| 亚洲午夜一区二区电影院| 精品亚洲A∨无码一区二区三区 | 无码乱人伦一区二区亚洲| 欧洲亚洲综合一区二区三区| 亚洲av日韩综合一区二区三区 | 一区二区不卡在线| 中文无码AV一区二区三区| 亚洲Av无码国产一区二区| 国产乱码伦精品一区二区三区麻豆| 国产自产在线视频一区| av无码免费一区二区三区| 无码精品视频一区二区三区| 人妻AV一区二区三区精品| 精品三级AV无码一区| 国产精品无码一区二区在线观一| 国产精品一区二区久久精品无码| 99精品久久精品一区二区| 国产一区二区三区在线2021 | 91一区二区三区| 91精品一区二区综合在线| 国产精品福利区一区二区三区四区| 日韩AV无码一区二区三区不卡毛片| 国产精品亚洲一区二区三区在线| 国产精品无码一区二区在线观| 亚洲综合一区二区三区四区五区| 免费人人潮人人爽一区二区 | 亚洲一区免费视频| 内射女校花一区二区三区| 美女AV一区二区三区| 国产成人精品一区二三区熟女| 精品无码一区二区三区爱欲九九| 久久久国产一区二区三区| 一区二区三区视频网站| 人妻体内射精一区二区三区 | 国产一区二区三区播放| 麻豆高清免费国产一区| 中文字幕亚洲一区二区三区| 国产剧情一区二区| 亚洲电影国产一区| 综合人妻久久一区二区精品|