多线程:AQS的一些心得
AQS作為JUC并發組件實現的核心。全稱是AbstructQueuedSynchronizer,也就是同步隊列器。
其內部實現主要的是一個state狀態標識和基于FIFO一個同步隊列。
這個狀態標識表明當前的資源是否是可以爭取的,當state=0是,表示資源沒有加鎖;當state>0時,表示資源加鎖。
關于這個隊列,有幾個屬性:
1)隊列是基于雙向鏈表的,因此具有定制的節點結構信息。node
2)tail尾指針,指向隊列的最后一個節點
3)頭指針,指向隊列的第一個節點(也即是? ?頭節點)。頭結點中包含有當前資源的同步狀態信息。
4)waitStatus 節點等待標識。
=1時? ?SIGNAL? ?表明當前節點的后繼節點所包含的線程需要運行。
=-1? ? CANCEL? 表明當前節點線程取消
=-2? ? CONDITION 表明當前節點在等待condition? 處于Condition等待隊列
=-3? ? PROPAGATE? 應用在共享鎖中
?= 0? ?當前節點在同步隊列中,等待獲取鎖
?
AQS是通過內部的同步隊列來完成線程獲取資源的排隊工作
AQS可以實現獨占鎖和共享鎖。
ReentrantLock實現的是獨占鎖;ReentrantReadWriteLock實現的是獨占鎖和共享鎖;CountDownLatch實現的是共享鎖。
因此,關于鎖的獲取和釋放有以下幾種方式:
1)獨占式獲取鎖
首先,線程嘗試獲得鎖tryAcquire,獲取成功則函數返回,獲得失敗,addWaiter構造一個獨占模式同步節點,如果前驅節點不為空則將該節點采用CAS方式放置到同步隊列尾部,如果放置失敗則說明當前節點位置存在并發競爭,進入enq方法進行添加,其實,enq方法也就是通過CAS自旋鎖的方式不斷地嘗試來向隊列中添加構造的節點;
如果節點最后成功地放置到隊列里。執行acquireQueued方法,當前節點中的線程通過自旋方式嘗試獲取同步狀態。
如果當前節點的前驅節點是頭結點,并且重新嘗試獲取鎖成功,就將當前節點設置為頭結點!否則判斷當前線程是否可以被安全阻塞掛起(根據waitStatus來判斷,判斷函數shouldParkAfterFailedAcquire),是的話就調用parkAndCheckInterrupt方法里的park方法進行掛起。然后再次進入for循環自旋。
如何判斷????
當前節點的前驅節點是waitStatus是SIGNAL則可以掛起,之后由前驅節點進行喚醒;
若前驅節點是CANCEL 則向前遍歷直到找到一個是SIGNAL的節點,并將當前節點放置在此節點的后面,不能掛起;
否則嘗試將前驅節點的狀態設置為SIGNAL,不能掛起
?
2)獨占式釋放鎖
調用release方法來釋放鎖。這個方法一般由子類自己重寫。
首先,嘗試釋放鎖tryRelease,如果成功那么就喚醒后繼節點? ? unparkSuccessor里的LockSupport.unpark方法
?
3)共享式獲得鎖
調用acquireShared方法,首先嘗試獲得鎖,獲得成功就返回;獲得失敗進入等待隊列。直到獲取到資源為止才返回。
若當前節點的前驅節點是頭結點,則嘗試進行加鎖;如果加鎖成功就調用setHeadAndPropagate,將當前節點設置為頭結點,喚醒后繼節點,并將之前的頭結點從隊列中踢出去,等待GC回收;
shared模式下是允許多個線程持有一把鎖的,其中tryAcquire的返回值標志了是否允許其他線程繼續進入。如果允許的話,需要喚醒隊列中等待的線程。其中setHeadAndPropagate方法中的doReleaseShared方法的邏輯很簡單,就是喚醒后繼線程。
如果獲得鎖失敗,判斷當前線程是否可以被安全阻塞掛起,是的話就調用parkAndCheckInterrupt方法里的park方法進行掛起。
tryAcquireShared返回值為正,說明獲得鎖成功,負數則失敗。
?
4)共享式釋放鎖
調用releaseShared方法;嘗試釋放鎖,釋放成功再次調用doReleaseShared,此方法內部還是調用LockSupport.unpark()方法喚醒后繼線程。
?
5)獨占超時獲得鎖
相比于獨占獲得鎖方式,最重要的區別就是:獨占獲得鎖在自旋獲得鎖失敗之后會掛起,讓出CPU時間片,之后再次進入for循環自旋嘗試獲得鎖;而獨占超時獲得鎖在獲得鎖失敗之后,判斷當前時間是否超時?若超時,則直接返回不再進行for循環;若沒有超時,則將當前線程阻塞指定的超時時間之后再次進行for循環進行CAS自旋獲取鎖!
調用doAcquireNanos方法。該方法在自旋過程中,當節點的前驅節點為頭節點時嘗試獲取同步狀態,如果獲取成功則從該方法返回,這個過程和獨占式同步獲取的過程類似,
但是在同步狀態獲取失敗的處理上有所不同。如果當前線程獲取同步狀態失敗,則首先重新計算超時間隔nanosTimeout,則判斷是否超時(nanosTimeout小于等于0表示已經超時),如果沒有超時,則使當前線程等待nanosTimeout納秒(當已到設置的超時時間,該線程會從LockSupport.parkNanos(Object blocker,long nanos)方法返回)。
如果nanosTimeout小于等于spinForTimeoutThreshold(1000納秒)時,將不會使該線程進行超時等待,而是進入快速的自旋過程。原因在于,非常短的超時等待無法做到十分精確,如果這時再進行超時等待,相反會讓nanosTimeout的超時從整體上表現得反而不精確。因此,在超時非常短的場景下,同步器會進入無條件的快速自旋。
?
詳情請參見本博客多線程模塊AQS源碼分析文章
?
總結
以上是生活随笔為你收集整理的多线程:AQS的一些心得的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 多线程:AQS源码分析
- 下一篇: Java集合:ArrayList和Lin