Java讀寫鎖ReadWriteLock

Charge8發表於2020-06-26

一、讀寫鎖ReadWriteLock

ReadWriteLock 是 JDK 中的讀寫鎖介面,提供了 readLock 和 writeLock 兩種鎖的操作機制,一個是讀鎖,一個是寫鎖。

ReadWriteLock同Lock一樣也是一個介面,ReentrantLock 是Lock的一種實現,ReentrantReadWriteLock 是 ReadWriteLock 的一種實現。

ReadWriteLock 中只有寫鎖支援Condition,讀鎖不支援,讀鎖呼叫 newCondition() 方法,會丟擲 UnsupportedOperationException 異常

讀寫鎖非常適合讀多寫少的場景。讀寫鎖與互斥鎖的一個重要區別是讀寫鎖允許多個執行緒同時讀共享變數,這是讀寫鎖在讀多寫少的情況下效能較高的原因。

 1、讀寫鎖的原則:

多個執行緒可同時讀共享變數

只允許一個執行緒寫共享變數

寫執行緒正在執行寫操作,禁止其他執行緒讀寫共享變數

2、讀寫鎖互斥原則:

讀-讀能共存,

讀-寫不能共存,

寫-寫不能共存。

例子:

public class ReadWriteLockDemo {

    final static ReadWriteLock rwLock = new ReentrantReadWriteLock();

    final static Lock readLock = rwLock.readLock();//讀鎖

    final static Lock writeLock = rwLock.writeLock();//寫鎖

    static int count = 0;

    public static void main(String[] args) {
        for (int i = 0; i < 100; i++) {
            new Thread(() -> {
                System.out.println(Thread.currentThread().getName() + "==" + get());
            }, "read").start();
        }

        for (int i = 0; i < 3; i++) {
            new Thread(() -> {
                System.out.println(Thread.currentThread().getName() + "==add");
                add(5);
            }, "write").start();
        }
    }

    private static int get() {
        //使用讀鎖
        readLock.lock();
        try {
            return count;
        } finally {
            readLock.unlock();
        }
    }

    private static void add(int num) {
        //使用寫鎖
        writeLock.lock();
        try {
            count = count + num;
        } finally {
            writeLock.unlock();
        }
    }
}

二、鎖的一些知識點

1、公平鎖和非公平鎖

概念

公平鎖:是指多個執行緒按照申請鎖的順序來獲取鎖,類似排隊打飯,先來後到。

非公平鎖:是指多個執行緒獲取鎖的順序並不是按照申請鎖的順序,有可能後申請的執行緒比先申請的執行緒優先獲取鎖,在高併發的情況下,有可能會造成優先順序反轉或者飢餓現象

公平鎖和非公平鎖區別

公平鎖:在併發壞境中.每個執行緒在獲取鎖時會先檢視此鎖維護的等待佇列,如果為空,或者當前執行緒是等待佇列的第一個,就佔有鎖.否則就會加入到等待佇列中.以後會按照FIFO的規則從佇列中取到自己。

非公平鎖:非公平鎖比較粗魯,上來就直接嘗試佔有鎖,如果嘗試失敗,就再採用類似公平鎖那種方式。

 

ReentrantLock 和 ReadWriteLock 而言,通過建構函式指定該鎖是否為公平鎖,預設是非公平鎖。非公平鎖的優點在於吞吐量比公平鎖大。

synchronized 而言,也是一種非公平鎖。

2、可重入鎖

可重入鎖,也叫做遞迴鎖,指的是同一執行緒外層函式獲得鎖之後 ,內層遞迴函式仍然有獲取該鎖的程式碼,但不受影響。

可重入鎖最大的作用是避免死鎖

可重入特性還允許從寫鎖降級到讀鎖—通過獲取寫鎖,然後獲取讀鎖,然後釋放寫鎖。但是,從讀鎖到寫鎖的升級是不可能的。

 

3、鎖降級

鎖降級指的是把持住(當前擁有的)寫鎖,再獲取到讀鎖,隨後釋放(先前有用的)寫鎖的過程。通過這種重入,可以減少一步流程(釋放寫鎖後 再次 獲取讀鎖)。使用了鎖降級,就可以減去釋放寫鎖的步驟。直接獲取讀鎖。效率更高。

當前執行緒擁有寫鎖,然後將其釋放,最後再獲取讀鎖,這種並不能稱之為鎖降級,

例項:

public class CachedData {
    //模擬共享資料
    private String data = "原來的資料";
    //volatile修飾,保持記憶體可見性,資料是不是最新的
    volatile boolean isUpdate;
    //可重入讀寫鎖
    final ReentrantReadWriteLock rwl = new ReentrantReadWriteLock();

    // 模擬放置資料到快取
    public void processCachedData(String d) {
        //獲取寫鎖之前,首先獲取讀鎖
        rwl.readLock().lock();
        //發現資料不是最新的則放棄讀鎖(讀鎖不能升級),獲取寫鎖
        if (!isUpdate) {
            rwl.readLock().unlock();
            rwl.writeLock().lock();
            try {
                if (!isUpdate) {
                    data = d;  //拿到寫鎖後,把新的資料寫入
                    isUpdate = true;
                }
                rwl.readLock().lock(); //擁有寫鎖的請況下金額直接獲取讀鎖
            } finally {
                //同時擁有讀鎖和寫鎖,在這裡釋放寫鎖,進行鎖降級
                rwl.writeLock().unlock();
            }
        }

        try {
            System.out.println("最新的資料列印:" + data);
        } finally {
            rwl.readLock().unlock();
        }
    }

    public static void main(String[] args) {
        CachedData cachedData = new CachedData();
        for (int i = 0; i < 10; i++) {
            new Thread(new Runnable() {
                @Override
                public void run() {
                    try {
                        TimeUnit.SECONDS.sleep(1);
                        cachedData.processCachedData(Thread.currentThread().getName() + "=新資料");
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }).start();
        }

    }
}

通常可以在集合使用場景中看到ReentrantReadWriteLock的使用。不過只有在集合比較大,讀操作比寫操作多,操作開銷大於同步開銷的時候才是值得的。

public class ReadWriteLockDemo2 {
    private final Map<String, Object> m = new TreeMap<String, Object>();
    //讀寫鎖
    private final ReentrantReadWriteLock rwl = new ReentrantReadWriteLock();
    //獲取讀鎖
    private final Lock r = rwl.readLock();
    //獲取寫鎖
    private final Lock w = rwl.writeLock();

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

    public String[] allKeys() {
        r.lock();
        try {
            Set<String> rsSet = m.keySet();
            return rsSet.toArray(new String[rsSet.size()]);
        } finally {
            r.unlock();
        }
    }

    public Object put(String key, Object value) {
        w.lock();
        try {
            return m.put(key, value);
        } finally {
            w.unlock();
        }
    }

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

 

—— Stay Hungry. Stay Foolish. 求知若飢,虛心若愚。

相關文章