閱讀189 返回首頁    go 汽車大全


顯式鎖(第十三章)

顯式鎖

在Java5.0之前,在協調對共享對象的訪問時可以使用的機製隻有synchronized和volatile。Java5.0增加了一種新的機製:ReentrantLock。ReentrantLock並不是一種替代內置加鎖的方法,而是當內置加鎖機製不適用時,作為一種可選擇的高級功能。

1. Lock接口與ReentrantLock

Lock提供了一種無條件的、可輪詢的、定時的以及可中斷的鎖獲取操作,所有加鎖和解鎖的方法都是顯示的。

Lock接口:

public interface Lock {
    void lock();
    void lockInterruptibly throws InterruptedException;
    boolean tryLock();
    boolean tryLock(long timeout, TimeUnit unit) throws InterruptedException;
    void unlock();
    Condition newCondition();
}

ReentrantLock實現了Lock接口,並提供了與synchronized相同的互斥性和內存可見性。

Lock接口的使用形式:

Lock lock = new ReentrantLock();
...
lock.lock();
try {
    //更新對象狀態
    //捕獲異常,並在必要時恢複不變性條件
} finally {
    lock.unlock();
}
    使用Lock時,必須在finally塊中釋放鎖,否則,如果被保護的代碼中拋出了異常,那麼這個鎖永遠都無法釋放。這就是ReentrantLock不能完全替代synchronized的原因:它更加危險,因為程序的執行控製離開被保護的代碼塊時,不會自動清除鎖。

2. 輪詢鎖與定時鎖

可定時的與可輪訓的鎖獲取模式是由tryLock方式實現的,與無條件的鎖獲取模式相比,它具有更完善的錯誤恢複機製。

    可定時的與可輪詢的鎖能夠避免死鎖的發生。如果不能獲得所有需要的鎖,那麼可以使用可定時的或可輪詢的鎖獲取方式,從而使你重新獲得控製權,它會釋放已經獲得的鎖,然後重新嚐試獲取所有鎖。

在實現具有時間限製的操作時,定時鎖能夠提供一個時限,如果操作不能在指定的時間內給出結果,那麼就會使程序提前結束。

3. 可中斷的鎖獲取操作

Java中,請求內置鎖時不能響應中斷。Lock的lockInterruptibly方法能夠在鎖的同時保持對中斷的響應,且由於它包含在Lock中,因此無須創建其他類型的不可中斷阻塞機製。

...
lock.lockInterruptibly();
...

定時的tryLock同樣能響應中斷,因此當需要實現一個定時的和可中斷的鎖獲取操作時,可是使用tryLock方法。

4. 公平性

在ReentrantLock的構造函數中提供了兩種公平性選擇:非公平(默認)、公平
在公平的鎖上,線程按照它們發出請求的順序來獲得鎖,但在非公平的鎖上,則允許“插隊”:當一個線程請求非公平的鎖時,如果在發出請求的同時該鎖的狀態變為可用,那麼這個線程將跳過隊列中所有的等待線程並獲得這個鎖。

    在大多數情況下,非公平鎖的性能要高於公平鎖的性能。

當持有鎖的時間相對較長,或者請求鎖的平均時間間隔較長,那麼應該使用公平鎖。在這些情況下,“插隊”帶來的吞吐量提升(當鎖處於可用狀態時,線程卻還處於被喚醒的過程中)則可能不會出現。

與默認的ReentrantLock一樣,內置鎖並不會提供確定的公平性保證,但在大多數情況下,在鎖實現上實現統計上的公平性保證已經足夠了。Java語言規範並沒有要求JVM以公平的方式來實現內置鎖,而在各種JVM中也沒有這樣做。ReentrantLock並沒有進一步降低鎖的公平性,而隻是使一些已經存在的內容更明顯。

5.在synchronized和ReentrantLock之間進行選擇

  • ReentrantLock的優點:
    ReentrantLock在加鎖和內存上提供的語義與內置鎖相同,此外它還提供了一些其他功能,包括定時的鎖等待、可中斷的鎖等待、公平性,以及實現非塊結構的加鎖

  • ReentrantLock的缺點:
    ReentrantLock的危險性比同步機製要高,如果忘記在finally塊中調用unlock,那麼就有可能出現問題。

    兩者之間的選擇:
    當需要一些高級功能時才應該使用ReentrantLock,這些功能包括:可定時的、可輪訓的與可中斷的鎖獲取操作,公平隊列,以及非塊結構的鎖。否則,還是應該優先使用synchronized。
    

6. 讀-寫鎖

一個資源可以被多個讀操作訪問,或者被一個寫操作訪問,但兩者不能同時進行。
要讀取由ReadWriteLock保護的數據,必須首先獲得讀取鎖,當需要修改ReadWriteLock保護的數據時,必須首先獲得寫入鎖。盡管這兩個鎖看上去是彼此獨立的,但讀取鎖和寫入鎖隻是讀-寫鎖對象的不同視圖。

public interface ReadWriteLock {
    Lock readLock();
    Lock writeLock();
}

ReentranReadWriteLock為這兩種鎖都提供了可重入的加鎖語義。與ReentrantLock類似,ReentrantReadWriteLock在構造時也可以選擇是一個非公平的鎖(默認)還是一個公平的鎖。
在公平的鎖中,等待事件最長的線程將優先獲得鎖。如果這個鎖由讀線程持有,而另一個線程請求寫入鎖,那麼其他讀線程都不能獲得讀取鎖,直到寫線程使用完並且釋放了寫入鎖。
在非公平的鎖中,線程獲得訪問許可的順序是不確定的。寫線程降級為讀線程是可以的,但從讀線程升級為寫線程則是不可以的(這樣做會導致死鎖)。

最後更新:2017-11-04 18:33:51

  上一篇:go  原子變量與非阻塞同步機製(第十五章)
  下一篇:go  性能與可伸縮性(第十一章)