1. 程式人生 > >java併發程式設計的藝術(三)---lock原始碼

java併發程式設計的藝術(三)---lock原始碼

 

jdk1.5以後,併發包中新增了lock介面,

它相對於synchronized,多了以下三個主要特性:嘗試非阻塞地獲取鎖(嘗試獲取鎖成功則持有)、能被中斷地獲取鎖(鎖的程序能響應中斷)、超時獲取鎖(指定時間截止之前獲取鎖)。

我們看看它介面中定義的api:

獲取鎖

可中斷地獲取鎖

嘗試非阻塞地獲取鎖,能夠獲取則返回true,否則false

超時獲取鎖,三種返回情況:1、當前執行緒在超時時間內獲得了鎖。2、當前執行緒在超時時間內被中斷。3、超時時間內沒獲得鎖

釋放鎖

獲取等待通知元件,該元件和當前的鎖繫結,當前執行緒只有獲得了鎖,才能呼叫該元件的wait()方法,而呼叫後,當前執行緒將釋放鎖。

java中lock的實現是ReentrantLock

 觀測其原始碼,我們可以發現,lock介面的所有實現方法均是通過一個AbstractQueuedSynchronizer(同步器)的物件實現的

佇列同步器是用來構建鎖或者其他同步元件的基礎框架,使用了一個int成員變量表示同步狀態,通過內建的FIFO佇列來完成資源獲取執行緒的排隊工作。

他有三個方法改變狀態

getState():獲取當前同步狀態

setState():設定當前同步狀態

compareAndSetState(): 使用CAS設定當前狀態,保證原子性

同步器中的佇列使用了一個雙向佇列來管理同步狀態

 

每個佇列元素都有三種狀態: cancelled:1 等待的執行緒超時或者被中斷,將取消等待變成該狀態(已取消)

signal: -1 下一個節點處於等待狀態,當前節點若是釋放了同步狀態或者被取消了,將通知後繼節點得以執行

condition: -2 節點在等待佇列中,等待在condition上,其他執行緒對condition呼叫了signal()方法後,該節點將從等待佇列中轉移到同步佇列中,加入到對同步狀態的獲取中。

propagate: -3 傳播共享式同步狀態

initial: 0 初始狀態

主要物件:

node prev:前驅節點

node next:後繼節點

node nextWaiter: 等待佇列中的後繼節點。

Thread thread: 獲取同步狀態的執行緒

同步器中儲存著頭節點head跟尾節點tail。

原始碼中實現lock的方法,是呼叫了同步器中的acquire(int arg)(獨佔式同步狀態)方法

該方法嘗試獨佔獲取同步狀態,成功則返回,否則進入同步佇列等待

 

通過死迴圈來保證節點的正確新增(CAS)

 

節點進入同步佇列後,進入一個自旋的過程,當獲得同步狀態則呼叫release從自旋退出,並喚醒頭節點的後繼節點

獨佔式同步狀態總結: 獲取同步狀態失敗的執行緒會進入到同步器佇列中進行自旋,移除佇列的條件是前驅節點為頭節點且成功獲取了同步狀態,在釋放同步狀態時候,同步器會呼叫tryRelease()方法釋放同步狀態並且喚醒後繼節點。

共享式同步狀態:主要用在讀寫操作上,多執行緒可以同時讀,但是寫鎖會排他。

同步器中的acquireShared()方法就是以共享式地獲取同步狀態

我們可以看到在ReentrantReadWriteLock中的讀鎖ReadLock中的lock()方法

呼叫了同步器中的共享式獲取同步狀態,而寫鎖writeLock中則呼叫了獨佔式方法

 重入鎖:支援執行緒對資源的重複加鎖,reentrantLock中先判斷當前執行緒是否為獲取鎖的執行緒,是則成功獲取,鎖獲取成功時候計數器加1,釋放時候減1,當獲取n次並釋放n次時候,計數器為0則表示當前鎖已經釋放,原始碼方法為nonfairTrAcquire()非公平鎖Nonfairsync

公平鎖的原始碼 FairSync 

兩者的比較就是判斷條件多了hasQueuedPredecessors()方法,就是加入了同步佇列中當前節點是否有前驅節點的判斷,如果方法返回true 則表示有執行緒更早請求獲取鎖,因此要等待前驅執行緒獲取並釋放鎖之後才能獲取。

為什麼會有公平鎖跟非公平鎖呢:

其實非公平鎖(預設)系統執行緒開銷更低,公平鎖按照佇列fifo的原則,進行了大量的執行緒切換,而非公平鎖,大部分是同一個執行緒又獲取到了鎖,進行了少量的執行緒切換,從而提升了效能。