1. 程式人生 > >Java多線程系列--“JUC鎖”03之 公平鎖(一)

Java多線程系列--“JUC鎖”03之 公平鎖(一)

map logs size spa bstr 通過 進入 中斷 images

基本概念

本章,我們會講解“線程獲取公平鎖”的原理;在講解之前,需要了解幾個基本概念。後面的內容,都是基於這些概念的;這些概念可能比較枯燥,但從這些概念中,能窺見“java鎖”的一些架構,這對我們了解鎖是有幫助的。
1. AQS -- 指AbstractQueuedSynchronizer類。
AQS是java中管理“鎖”的抽象類,鎖的許多公共方法都是在這個類中實現。AQS是獨占鎖(例如,ReentrantLock)和共享鎖(例如,Semaphore)的公共父類。

2. AQS鎖的類別 -- 分為“獨占鎖”和“共享鎖”兩種。
(01) 獨占鎖 -- 鎖在一個時間點只能被一個線程鎖占有。根據鎖的獲取機制,它又劃分為“公平鎖

”和“非公平鎖”。 公平鎖,是按照通過CLH等待線程按照先來先得的規則,公平的獲取鎖;而非公平鎖,則當線程要獲取鎖時,它會無視CLH等待隊列而直接獲取鎖。獨占鎖的典 型實例子是ReentrantLock,此外,ReentrantReadWriteLock.WriteLock也是獨占鎖。
(02) 共享鎖 -- 能被多個線程同時擁有,能被共享的鎖。JUC包中的ReentrantReadWriteLock.ReadLock,CyclicBarrier, CountDownLatch和Semaphore都是共享鎖。這些鎖的用途和原理,在以後的章節再詳細介紹。

3. CLH隊列 -- Craig, Landin, and Hagersten lock queue
CLH隊列是AQS中“等待鎖”的線程隊列。在多線程中,為了保護競爭資源不被多個線程同時操作而起來錯誤,我們常常需要通過鎖來保護這些資源。在獨占鎖 中,競爭資源在一個時間點只能被一個線程鎖訪問;而其它線程則需要等待。CLH就是管理這些“等待鎖”的線程的隊列。
CLH是一個非阻塞的 FIFO 隊列。也就是說往裏面插入或移除一個節點的時候,在並發條件下不會阻塞,而是通過自旋鎖和 CAS 保證節點插入和移除的原子性。

4. CAS函數 -- Compare And Swap
CAS函數,是比較並交換函數,它是原子操作函數;即,通過CAS操作的數據都是以原子方式進行的。例如,compareAndSetHead(), compareAndSetTail(), compareAndSetNext()等函數。它們共同的特點是,這些函數所執行的動作是以原子的方式進行的。

本章是圍繞 “公平鎖”如何獲取鎖而層次展開。“公平鎖”涉及到的知識點比較多,但總的來說,不是特別難;如果讀者能讀懂AQS和 ReentrantLock.java這兩個類的大致意思,理解鎖的原理和機制也就不成問題了。本章只是作者本人對鎖的一點點理解,希望這部分知識能幫助 您了解“公平鎖”的獲取過程,認識“鎖”的框架。

ReentrantLock數據結構

ReentrantLock的UML類圖

技術分享

從圖中可以看出:
(01) ReentrantLock實現了Lock接口。
(02) ReentrantLock與sync是組合關系。ReentrantLock中,包含了Sync對象;而且,Sync是AQS的子類;更重要的 是,Sync有兩個子類FairSync(公平鎖)和NonFairSync(非公平鎖)。ReentrantLock是一個獨占鎖,至於它到底是公平鎖 還是非公平鎖,就取決於sync對象是"FairSync的實例"還是"NonFairSync的實例"。

獲取公平鎖(基於JDK1.7.0_40)

我們知道,獲取鎖是通過lock()函數。下面,我們以lock()對獲取公平鎖的過程進行展開。

1. lock()

lock()在ReentrantLock.java的FairSync類中實現,它的源碼如下:

final void lock() {
    acquire(1);
}

說明:“當前線程”實際上是通過acquire(1)獲取鎖的。
這裏說明一下“1”的含義,它是設置“鎖的狀態”的參數。對於“獨占鎖”而言,鎖處於可獲取狀態時,它的狀態值是0;鎖被線程初次獲取到了,它的狀態值就變成了1。
由於ReentrantLock(公平鎖/非公平鎖)是可重入鎖,所以“獨占鎖”可以被單個線程多此獲取,每獲取1次就將鎖的狀態+1。也就是說,初次獲 取鎖時,通過acquire(1)將鎖的狀態值設為1;再次獲取鎖時,將鎖的狀態值設為2;依次類推...這就是為什麽獲取鎖時,傳入的參數是1的原因 了。
可重入就是指鎖可以被單個線程多次獲取。

2. acquire()

acquire()在AQS中實現的,它的源碼如下:

public final void acquire(int arg) {
    if (!tryAcquire(arg) &&
        acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
        selfInterrupt();
}

(01) “當前線程”首先通過tryAcquire()嘗試獲取鎖。獲取成功的話,直接返回;嘗試失敗的話,進入到等待隊列排序等待(前面還有可能有需要線程在等待該鎖)。
(02) “當前線程”嘗試失敗的情況下,先通過addWaiter(Node.EXCLUSIVE)來將“當前線程”加入到"CLH隊列(非阻塞的FIFO隊列)"末尾。CLH隊列就是線程等待隊列。
(03) 再執行完addWaiter(Node.EXCLUSIVE)之後,會調用acquireQueued()來獲取鎖。由於此時ReentrantLock是公平鎖,它會根據公平性原則來獲取鎖。
(04) “當前線程”在執行acquireQueued()時,會進 入到CLH隊列中休眠等待,直到獲取鎖了才返回!如果“當前線程”在休眠等待過程中被中斷過,acquireQueued會返回true,此時"當前線 程"會調用selfInterrupt()來自己給自己產生一個中斷。至於為什麽要自己給自己產生一個中斷,後面再介紹。

一. tryAcquire()

1. tryAcquire()

公平鎖的tryAcquire()在ReentrantLock.java的FairSync類中實現,源碼如下:

參考:

http://www.cnblogs.com/skywang12345/p/3496147.html

Java多線程系列--“JUC鎖”03之 公平鎖(一)