深入淺出 Java Concurrency (13): 鎖機制 part 8 讀寫鎖 (ReentrantReadWriteLock) (1)

來源:互聯網
上載者:User

標籤:write   ret   擷取   需要   opera   current   說明   導致   大量   

 

從這一節開始介紹鎖裡面的最後一個工具:讀寫鎖(ReadWriteLock)。

ReentrantLock 實現了標準的互斥操作,也就是一次只能有一個線程持有鎖,也即所謂獨佔鎖的概念。前面的章節中一直在強調這個特點。顯然這個特點在一定程度上面減低了輸送量,實際上獨佔鎖是一種保守的鎖策略,在這種情況下任何“讀/讀”,“寫/讀”,“寫/寫”操作都不能同時發生。但是同樣需要強調的一個概念是,鎖是有一定的開銷的,當並發比較大的時候,鎖的開銷就比較客觀了。所以如果可能的話就盡量少用鎖,非要用鎖的話就嘗試看能否改造為讀寫鎖。

ReadWriteLock描述的是:一個資源能夠被多個讀線程訪問,或者被一個寫線程訪問,但是不能同時存在讀寫線程。也就是說讀寫鎖使用的場合是一個共用資源被大量讀取操作,而只有少量的寫操作(修改資料)。清單1描述了ReadWriteLock的API。

 清單1 ReadWriteLock 介面

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

清單1描述的ReadWriteLock結構,這裡需要說明的是ReadWriteLock並不是Lock的子介面,只不過ReadWriteLock藉助Lock來實現讀寫兩個視角。在ReadWriteLock中每次讀取共用資料就需要讀取鎖,當需要修改共用資料時就需要寫入鎖。看起來好像是兩個鎖,但其實不盡然,在下一節中的分析中會解釋這點奧秘。

在JDK 6裡面ReadWriteLock的實現是ReentrantReadWriteLock。

清單2 SimpleConcurrentMap

package xylz.study.concurrency.lock;

import java.util.ArrayList;
import java.util.Collection;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;

public class SimpleConcurrentMap<K, V> implements Map<K, V> {

    final ReadWriteLock lock = new ReentrantReadWriteLock();

    final Lock r = lock.readLock();

    final Lock w = lock.writeLock();

    final Map<K, V> map;

    public SimpleConcurrentMap(Map<K, V> map) {
        this.map = map;
        if (map == null) throw new NullPointerException();
    }

    public void clear() {
        w.lock();
        try {
            map.clear();
        } finally {
            w.unlock();
        }
    }

    public boolean containsKey(Object key) {
        r.lock();
        try {
            return map.containsKey(key);
        } finally {
            r.unlock();
        }
    }

    public boolean containsValue(Object value) {
        r.lock();
        try {
            return map.containsValue(value);
        } finally {
            r.unlock();
        }
    }

    public Set<java.util.Map.Entry<K, V>> entrySet() {
        throw new UnsupportedOperationException();
    }

    public V get(Object key) {
        r.lock();
        try {
            return map.get(key);
        } finally {
            r.unlock();
        }
    }

    public boolean isEmpty() {
        r.lock();
        try {
            return map.isEmpty();
        } finally {
            r.unlock();
        }
    }

    public Set<K> keySet() {
        r.lock();
        try {
            return new HashSet<K>(map.keySet());
        } finally {
            r.unlock();
        }
    }

    public V put(K key, V value) {
        w.lock();
        try {
            return map.put(key, value);
        } finally {
            w.unlock();
        }
    }

    public void putAll(Map<? extends K, ? extends V> m) {
        w.lock();
        try {
            map.putAll(m);
        } finally {
            w.unlock();
        }
    }

    public V remove(Object key) {
        w.lock();
        try {
            return map.remove(key);
        } finally {
            w.unlock();
        }
    }

    public int size() {
        r.lock();
        try {
            return map.size();
        } finally {
            r.unlock();
        }
    }

    public Collection<V> values() {
        r.lock();
        try {
            return new ArrayList<V>(map.values());
        } finally {
            r.unlock();
        }
    }

}

清單2描述的是用讀寫鎖實現的一個安全執行緒的Map。其中需要特別說明的是並沒有實現entrySet()方法,這是因為實現這個方法比較複雜,在後面章節中講到ConcurrentHashMap的時候會具體談這些細節。另外這裡keySet()和values()也沒有直接返回Map的視圖,而是一個映射原有元素的新視圖,其實這個entrySet()一樣,是為了保護原始Map的資料邏輯,防止不正確的修改導致原始Map發生資料錯誤。特別說明的是在沒有特別需求的情況下沒有必要按照清單2寫一個安全執行緒的Map實現,因為ConcurrentHashMap已經完成了此操作。

 

ReadWriteLock需要嚴格區分讀寫操作,如果讀操作使用了寫入鎖,那麼降低讀操作的輸送量,如果寫操作使用了讀取鎖,那麼就可能發生資料錯誤。

另外ReentrantReadWriteLock還有以下幾個特性:

  • 公平性
    • 非公平鎖(預設) 這個和獨佔鎖的非公平性一樣,由於讀線程之間沒有鎖競爭,所以讀操作沒有公平性和非公平性,寫操作時,由於寫操作可能立即擷取到鎖,所以會延遲一個或多個讀操作或者寫操作。因此非公平鎖的輸送量要高於公平鎖。
    • 公平鎖 利用AQS的CLH隊列,釋放當前保持的鎖(讀鎖或者寫鎖)時,優先為等待時間最長的那個寫線程分配寫入鎖,當前前提是寫線程的等待時間要比所有讀線程的等待時間要長。同樣一個線程持有寫入鎖或者有一個寫線程已經在等待了,那麼試圖擷取公平鎖的(非重入)所有線程(包括讀寫線程)都將被阻塞,直到最先的寫線程釋放鎖。如果讀線程的等待時間比寫線程的等待時間還有長,那麼一旦上一個寫線程釋放鎖,這一組讀線程將擷取鎖。
  • 重入性
    • 讀寫鎖允許讀線程和寫線程按照請求鎖的順序重新擷取讀取鎖或者寫入鎖。當然了只有寫線程釋放了鎖,讀線程才能擷取重入鎖。
    • 寫線程擷取寫入鎖後可以再次擷取讀取鎖,但是讀線程擷取讀取鎖後卻不能擷取寫入鎖。
    • 另外讀寫鎖最多支援65535個遞迴寫入鎖和65535個遞迴讀取鎖。
  • 鎖降級
    • 寫線程擷取寫入鎖後可以擷取讀取鎖,然後釋放寫入鎖,這樣就從寫入鎖變成了讀取鎖,從而實現鎖降級的特性。
  • 鎖定擴大
    • 讀取鎖是不能直接升級為寫入鎖的。因為擷取一個寫入鎖需要釋放所有讀取鎖,所以如果有兩個讀取鎖視圖擷取寫入鎖而都不釋放讀取鎖時就會發生死結。
  • 鎖擷取中斷
    • 讀取鎖和寫入鎖都支援擷取鎖期間被中斷。這個和獨佔鎖一致。
  • 條件變數
    • 寫入鎖提供了條件變數(Condition)的支援,這個和獨佔鎖一致,但是讀取鎖卻不允許擷取條件變數,將得到一個UnsupportedOperationException異常。
  • 重入數
    • 讀取鎖和寫入鎖的數量最大分別只能是65535(包括重入數)。這在下節中有介紹。

上面幾個特性對讀寫鎖的理解很有協助,而且也是必要的,另外在下一節中講ReadWriteLock的實現會用到這些知識的。

深入淺出 Java Concurrency (13): 鎖機制 part 8 讀寫鎖 (ReentrantReadWriteLock) (1)

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.