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的原則,進行了大量的執行緒切換,而非公平鎖,大部分是同一個執行緒又獲取到了鎖,進行了少量的執行緒切換,從而提升了效能。