JAVA併發編程梳理與學習七(AQS)

一、AQS(AbstractQueuedSynchronizer)定義及作用
同步隊列器,可以用來構建鎖或者其他同步組件,它使用volatitle定義了一個int型的同步變量表示同步狀態,通過內置的FIFO隊列來完成競爭資源線程的調度工作。這個是併發大神Doug Lea設計的
二、AQS使用思路
我們看AbstractQueuedSynchronizer方法,它是一個抽象類,自己本身並沒有實現任何同步方法,裏面只是定義了一些獲取、改變同步狀態來給自定義開發同步組件的開發者使用,既可以實現獨佔式鎖(ReenTrantLock),也可以實現共享鎖(ReenTrantReadWriteLock)。
既然是一個抽象類,我們要使用它就只能繼承,我們觀察ReenTrantLock源碼發現ReenTrantLock並沒有直接繼承AQS,而是通過一個抽象靜態內部類Sync來繼承AQS並實現AQS內部方法來管理同步狀態state,通過getState()、setState(int newState)、compareAndSetState(int expect, int update)來管理同步狀態。
爲什麼Doug Lea大師這麼設計呢?爲什麼不直接繼承兒通過內部類來繼承呢?
我們可以這麼理解大師的設計:
鎖是面向使用者的,它定義了使用者與鎖的交互,比如我們線程中使用,直接new就行了,隱藏了實現細節。
AQS同步器面向的是實現者,它簡化了鎖的實現方式,屏蔽了同步狀態管理、線程的等待、排隊、喚醒等底層操作。
鎖和同步器很好的隔離了使用者和實現者所關注的領域
實現者需要繼承同步器並重寫指定方法,隨後將同步器放到自己定義的同步組件(比如:鎖)中,然後調用同步器提供的模板方法,這些模板方法會調用使用者重寫的方法。
三、AQS中體現的設計模式中–模板方法模式
模板方法模式:模板方法,按我們日常理解,比如我們寫簡歷有一個簡歷模板,定義好一個框架,然後每個人在模板上根據自己的情況寫自己的技術棧、自己的經歷啥的。模板方法就是如此設計的,在父類中定義一個操作中的算法骨架,而將一些步驟的實現延遲到子類中,可以使子類不改變算法結構即可重新定義該算法的特定步驟。我們最常見的就是Spring框架裏的各種Template
我們把AQS提供模板方法分類
在這裏插入圖片描述
下面是可重寫的
在這裏插入圖片描述
訪問或者修改同步狀態的方法
getState():獲取同步狀態
setState():設置當前同步狀態
compareAndSetState(int expect,int update):使用CAS設置同步狀態,該方法能保證狀態設定的原子性
四、AQS源碼分析
1.AQS數據結構–節點和同步隊列
(1)節點note
AQS是CLH隊列鎖的一種變體實現,既然是隊列鎖就要有一個數據結構來存儲一些需要用到的數據,比如節點的狀態、前驅節點等,這個數據結構就是AQS中的靜態內部類Note.
我們可以看看源碼裏面的數據結構,看看是怎麼樣的
在這裏插入圖片描述
注意:前驅和後繼節點,比如我們排隊自己打飯,我們必須知道我們前面的是誰,這樣他打完飯後才能把勺子交給我們,我們也必須知道我們後面是誰,這樣我們打完飯後才能知道把勺子交給誰
過程:當線程獲取同步狀態失敗時,同步器會將當前線程、等待狀態等構造成一個節點Node並加入同步隊列中,並阻塞當前線程,當同步狀態釋放時,會把隊列中首節點的線程喚醒,使其再次獲取同步狀態。
Node:保存的使獲取同步狀態失敗線程的引用、等待狀態、前驅和後繼節點
(2)首節點head和尾節點tail
AQS還有一個首節點head執行隊列頭部,尾節點tail指向隊列尾部。我們看看源碼發現,首節點是不保存線程信息的節點。
。
在這裏插入圖片描述
2.AQS源碼分析
我們用Lock主要用lock方法和unlock方法,我們重點看這2個方法的源碼,我們以ReentrantLock爲例,ReentrantLock默認是非公平鎖,我們先以非公平鎖爲例子。我們前面說過
(1)在這裏插入圖片描述
(2)我們進入方法,先進入tryAcquire這個方法,點擊進去tryAcquire方法,我們發現AQS下面是一個空實現,上面我們也說過這個是個模板方法,需要我們自己去自己鎖裏面去重寫,我們看看ReentrantLock是如何重寫的,點進入發現進入nonfairTryAcquire這個方法,我們進入nonfairTryAcquire這個方法看看
在這裏插入圖片描述
(3)我們再回到acquire方法,tryAcquire(arg)這裏返回false,會進入 acquireQueued(addWaiter(Node.EXCLUSIVE), arg))方法,我們先看addWaiter(Node.EXCLUSIVE), arg),進入addWaiter方法
在這裏插入圖片描述
下面是enq方法
在這裏插入圖片描述
(4)addWaiter(Node.EXCLUSIVE), arg)方法就是把當前線程打包成一個節點,通過自旋CAS加入隊列,讓他成爲隊列的尾部,下面我們來看acquireQueued方法
在這裏插入圖片描述(5)上面未獲得鎖得線程已經處於阻塞狀態,下面我們看如何解除阻塞狀體,重新去競爭鎖,我們先看unlock方法
在這裏插入圖片描述進入release方法首先看到得是tryRelease(arg),我們上面說了tryRelease(arg)方法也是AQS得模板方法,我們去ReentrantLock看如何重寫的
在這裏插入圖片描述
下面回到release方法
在這裏插入圖片描述
我們再看看unparkSuccessor是如何喚醒下一個節點的
在這裏插入圖片描述
(6)喚醒下一個線程後,會重新進入(4)中的自旋死循環去重新拿鎖。
上面是獨佔式的非公平鎖的源碼及實現過程,我們總結一下:
在這裏插入圖片描述
其他非公平鎖、共享鎖都差不多,不過重寫方法不同,有興趣可以去自己看看

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章