java併發編程實踐學習(13 ) 顯示鎖

一.Lock和ReentrantLock

與內部加鎖機制不同,lock提供了無條件的,可輪詢的,定時的,可中斷的鎖獲取操作,所有的加鎖和解鎖的方法都是顯示的。
lock的鎖更加複雜:鎖必須在finally中釋放,另一個方面,如果鎖守護的代碼在try塊之外拋出了異常,它永遠不會被釋放;如果對象能夠被置於不一致的狀態,可能需要額外的try-catch,或try-finally。

1.可輪詢的和可定時的鎖請求

可定時的和可輪詢的鎖獲取模式,是由tryLock方法實現。與無條件的鎖獲取相比它它具有更完善的錯誤恢復機制。內部鎖中唯一的恢復方法是重行啓動程序,唯一的構建方法是構建程序時不要出錯。可輪詢和可定時的鎖提供了另一個鎖選擇:可以避免死鎖的發生。
如果你不能獲取所有需要的鎖,那麼使用可定時的和可輪詢的獲取方式使你能夠重新拿到控制權,它會釋放你已經獲得的這些鎖,然後再重新嘗試。(至少會記錄這個失敗,或採取其他措施)。
對於那些具有時間限制的活動,當它們調用了阻塞方法,定時鎖能夠在時間預算內設定響應的超時。如果活動在時間內沒能獲得結果,這個機制使程序能夠提前返回。使用內部鎖一旦開始請求,鎖就不能停止了。

2.可中斷的鎖獲取操作

可中斷的鎖獲取操作允許在可取消的活動中使用。當你正在響應中斷的是,lockInterruptibly方法使你能獲得鎖,並且由於它是內置Lock的,因此你不必在創建其他種類不可中斷的阻塞機制。
使用tryLock避免順序死鎖

public boolean transferMoney(final Account fromAccount, final Account toAccount, final DollarAmount amount,long timeout, TimeUnit unit) throws InterruptedException {  
    long fixedDelay = 123;  
    long randMod = 456;  
    long stopTime = System.nanoTime() + unit.toNanos(timeout);  
    while (true) {  
        if (fromAccount.getLock().tryLock()) { // 如果不能獲得鎖就返回重試  
            try {  
                if (toAccount.getLock().tryLock()) {  
                    try {  
                        if (fromAccount.getBalance().compareTo(amount) < 0) {  
                            throw new RuntimeException();  
                        } else {  
                            fromAccount.debit(amount);  
                            toAccount.credit(amount);  
                        }  
                    } finally {  
                        toAccount.getLock().unlock();  
                    }  
                }  
            } finally {  
                fromAccount.getLock().unlock();  
            }  
        }         
        if (System.nanoTime() > stopTime) { // 重試了指定次數仍然無法獲得鎖則返回失敗  
            return false;  
        }  
        Thread.sleep(fixedDelay + new Random().nextLong() % randMod);  
    }  
}  

使用預定時間的鎖

public boolean trySendOneSharedLine(String message, long timeout, TimeUnit unit) throws InterruptedException {  
    long nanosToLock = unit.toNanos(timeout) - estimatedNanosToSend(message);  
    if (lock.tryLock(nanosToLock, TimeUnit.NANOSECONDS)) {  
        return false;  
    }  

    try {  
        return sendOnSharedLined(message);  
    }finally {  
        lock.unlock();  
    }  
}  

可中斷的鎖獲取請求

public boolean sendOnSharedLine(String message)throws InterruptedException{
    lock.lockInterruptibly();
    try{
        return cancellableSendOnSharedLine(message);
    }finally{
        lock.unlock();
    }
}
private boolean cancellableSendOnSharedLine(String message)throws InterruptedException{
    ...
}

3.非塊結構的鎖

在內部鎖中,獲取和釋放這樣的行爲是塊結構的-總是在其獲得的相同的基本程序塊中釋放,而不考慮控制權是如何退出阻塞塊的。
在鏈表中,我們可以通過每個鏈表節點應用分離鎖來減小鎖的粒度,給定節點的鎖守護鏈接的指針,所以要遍歷或修改鏈表,我們必須得到這個鎖,並持有它直到我們獲得了下一個鎖;這之後我盟才能釋放前一個鎖。這項技術被稱作連式鎖,或者鎖聯接。

二.對性能的考量

當ReentrantLock被加入到java5.0時它提供的競爭上的性能要遠遠優於內部鎖。

1.公平性

ReentrantLock構造函數提供了倆種公平性選擇:創建非公平鎖(默認)或者公平鎖。
線程按順序請求獲得公平鎖,非公平鎖允許“闖入”:當請求到這樣的鎖時,如果鎖的狀態變爲可用,線程的請求可以在等待線程的隊列中向前跳躍獲得該鎖。(Semaphore同樣提供了公平的和非公平的獲取順序)。

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

內部鎖相比於顯示鎖有很大的優勢。它更加簡潔。ReentrantLock絕對是最危險的同步工具。內部鎖與ReentrantLock相比,還有另外一個優點:線程轉儲能夠顯示哪些個調用框架獲得了哪些鎖,並能夠識別發生了死鎖的那些線程。
java中提供了管理和調試接口,可以使用這個接口進行註冊,並通過其他管理和調試接口,從線程轉儲中得到ReentrantLock的加鎖信息。
未來的性能改進可能更傾向於synchronized;因爲它是內置於JVM的,它能夠進行優化。

在內部鎖不能足夠使用時,ReentrantLock才被作爲更高級的工具使用。

5.讀寫鎖

ReentrantLock實現了標準互斥鎖;一次最多隻有一個線程能夠持有相同ReentrantLock。但是互斥鎖過分的限制了併發性。
讀寫鎖:一個資源能夠被多個讀者訪問,或者被一個寫者訪問,兩者不能同時進行。與Lock一樣ReadWriteLock允許多種實現,造成了性能、調度保證、獲取優先、公平性、以及加鎖語義方面不盡相同。
在頻繁讀的情況下讀寫鎖能夠改進性能,在其他情況下允許的情況下比獨佔鎖要稍差一些,這歸根於它更大的複雜性。
讀取和寫入的互動有多種實現。ReadWriteLock的一些實現選擇如下:

  • 釋放優先。當寫者釋放寫入鎖,並且讀者和寫者都排在隊列中,應該選擇哪一個,讀者寫者,還是先請求的那個?
  • 讀者闖入。如果鎖由讀者獲得,但有寫者正在等待,那麼寫到達的寫者應該被授予讀取的權利麼?還是應該等待?允許讀者闖入到寫者之前提高了併發性,但是卻帶來了寫者飢餓的問題。
  • 重進入。讀取鎖和寫入鎖允許重進入嗎?
  • 降級。如果線程持有寫入的鎖,它能夠在不釋放鎖的情況下獲取讀取的鎖嗎?這可能造成寫者“降級”爲一個讀取鎖,同時不允許其它寫着修改這個被守護的資源
  • 升級。讀取鎖能夠優先於其他的讀者和寫者升級爲一個寫入鎖麼?大多數讀寫鎖時間並不支持升級,因爲在沒有顯示的升級的操作的情況下,很容易造成死鎖。(如果倆個讀者同時試圖升級到同一個寫入鎖,並不釋放讀取鎖)。

ReentrantReadWriteLock爲倆個鎖提供了可重進入的加鎖語義。它能夠被構造爲非公平(默認)或者是公平的。在公平的鎖中,選擇權交給等待時間最長的線程;如果鎖由讀者獲得,而一個線程請求寫入鎖,那麼不再允許讀者獲得讀取鎖。直到寫者被受理,並且已經釋放了寫入鎖,在非公平鎖中線程允許訪問順序是不定的。由寫者降級爲讀者是允許的;從讀者升級爲寫者是不允許的。
用讀寫鎖包裝的map

public class ReadWriteMap <K,V> {  
    private final Map<K,V> map;  
    private final ReadWriteLock lock = new ReentrantReadWriteLock();  
    private final Lock r = lock.readLock();  
    private final Lock w = lock.writeLock();  

    public ReadWriteMap(Map<K, V> map){  
        this.map = map;  
    }  

    public void put(K key, V value){  
        w.lock();  
        try{  
            return map.put(key, value);  
        }finally{  
            w.unlock();  
        }  
    }//remove(), putAll(), clear()也完全類似  

    public V get(Object key){  
        r.lock();  
        try{  
            return map.get(key);  
        }finally{  
            r.unlock();  
        }  
    }//對於其他的只讀Map方法也完全類似  
}  
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章