不管是内部鎖還是Lock都是獨占鎖,或者稱之排他鎖,即讀寫互斥、寫寫互斥、讀讀互斥;與排他鎖相對的另一種鎖是共享鎖,Java的ReadWriteLock是一種共享鎖,提供讀讀共享,但讀寫和寫寫仍然互斥。
ReadWriteLock最大的特性就是讀讀共享,比如A線程讀鎖正在進行讀取操作,此時如果B線程請求讀鎖,那麼B線程可以馬上順利獲得讀鎖而無需等待,但此時如果C線程請求寫鎖,那麼C線程需要等待鎖可用。ReadWriteLock由于提供了讀讀共享而增加了複雜性,是以在讀寫都相當頻繁的場景并不能展現出性能優勢,隻有在讀操作極多而寫操作極少的場景下才能展現其性能優勢。比如,一個應用系統安裝完成後需要導入一批維護性的初始化資料,這些資料可以通過界面修改,但需要修改的情況極少,當系統一啟動就會自動加載初始化資料到指定資料結構(如HashMap)供各個子產品讀取使用,那麼可以為這些資料的讀寫加ReadWriteLock,以提高讀取性能并保持資料的一緻性。
ReentrantReadWriteLock類是ReadWriteLock接口的一個實作,它與ReentrantLock類一樣提供了公平競争與不公平競争兩種機制,預設也是使用非公平競争機制。ReentrantLock是排他鎖,使用非公平競争機制時,搶占的機會相對還是比較少的,隻有當新請求恰逢鎖釋放時才有機會搶占,是以發生線程饑餓的現象幾乎很少。然而ReentrantReadWriteLock是共享鎖,或者說讀讀共享,并且經常使用于讀多寫少的場景,即請求讀操作的線程多而頻繁而請求寫操作的線程極少且間隔長,在這種場景下,使用非公平競争機制極有可能造成寫線程饑餓。比如,R1線程此時持有讀鎖且在進行讀取操作,W1線程請求寫鎖是以需要排隊等候,在R1釋放鎖之前,如果R2,R3,...,Rn 不斷的到來請求讀鎖,因為讀讀共享,是以他們不用等待馬上可以獲得鎖,如此下去W1永遠無法獲得寫鎖,一直處于饑餓狀态。是以使用ReentrantReadWriteLock類時,小心選擇公平機制,以免遇到出乎預料的結果。
最後,Java5的讀寫鎖實作有瑕疵,可能發生死鎖,在Java6已經修複,是以避免使用Java5讀寫鎖。
示例代碼: