ReentrantReadWriteLock讀寫鎖

概述

​ ReentrantReadWriteLock是Lock的另外一種實現方式,咱們已經知道了ReentrantLock是一個排他鎖,同一時間只容許一個線程訪問,而ReentrantReadWriteLock容許多個讀線程同時訪問,但不容許寫線程和讀線程、寫線程和寫線程同時訪問。相對於排他鎖,提升了併發性。在實際應用中,大部分狀況下對共享數據(如緩存)的訪問都是讀操做遠多於寫操做,這時ReentrantReadWriteLock可以提供比排他鎖更好的併發性和吞吐量。javascript

​ 讀寫鎖內部維護了兩個鎖,一個用於讀操做,一個用於寫操做。全部 ReadWriteLock實現都必須保證 writeLock操做的內存同步效果也要保持與相關 readLock的聯繫。也就是說,成功獲取讀鎖的線程會看到寫入鎖以前版本所作的全部更新。java

ReentrantReadWriteLock支持如下功能:數據庫

  1. 支持公平與非公平的獲取鎖方式。
  2. 支持可重入,讀線程獲取讀鎖後還能夠獲取讀鎖,可是不能獲取寫鎖;寫線程獲取寫鎖後既能夠再次獲取寫鎖還能夠獲取讀鎖。
  3. 容許從寫鎖降級爲讀鎖,其實現方式是:先獲取寫鎖,而後獲取讀鎖,最後釋放寫鎖。可是,從讀鎖升級到寫鎖是不能夠的;
  4. 讀取鎖和寫入鎖都支持鎖獲取期間的中斷;
  5. Condition支持。僅寫入鎖提供了一個 Conditon 實現;讀取鎖不支持 Conditon ,readLock().newCondition() 會拋出 UnsupportedOperationException。

使用場景

示例一:利用重入執行升級緩存後的鎖降級

在緩存有效的狀況下,支持併發讀。緩存失效,只容許獨佔寫。緩存

import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;

public class HibernateCache {

    /* 定義一個Map來模擬緩存 */
    private Map<String, Object> cache = new HashMap<String, Object>();

    /* 建立一個讀寫鎖 */
    private ReadWriteLock rwLock = new ReentrantReadWriteLock();

    /** * 模擬Hibernate緩存,優先緩存,若緩存不存在寫鎖更新 * * @param key * @return */
    public Object getData(String key) {

        /* 上讀鎖 */
        rwLock.readLock().lock();
        /* 定義從緩存中讀取的對象 */
        Object value = null;

        try {
            /* 從緩存中讀取數據 */
            value = cache.get(key);

            if (value == null) {
                /* 若是緩存中沒有數據,咱們就把讀鎖關閉,直接上寫鎖【讓一個線程去數據庫中取數據】 */
                rwLock.readLock().unlock();
                /* 上寫鎖 */
                rwLock.writeLock().lock();

                try {
                    /* 上了寫鎖以後再判斷一次【咱們只讓一個線程去數據庫中取值便可,當第二個線程過來的時候,發現value不爲空了就去緩存中取值】 */
                    if (value == null) {
                        /* 模擬去數據庫中取值 */
                        value = "hello";
                        System.out.println("修改換緩存");
                        cache.put(key, value);
                    }
                } finally {
                    /* 寫完以後把寫鎖關閉 */
                    rwLock.writeLock().unlock();
                }
                /* 緩存中已經有了數據,咱們再把已經 關閉的讀鎖打開 */
                rwLock.readLock().lock();
            }
            return value;

        } finally {
            /* 最後把讀鎖也關閉 */
            rwLock.readLock().unlock();
        }

    }

    public Map<String, Object> getCache() {
        return cache;
    }

    public void setCache(Map<String, Object> cache) {
        this.cache = cache;
    }
}
複製代碼

示例二:高併發讀寫共享數據

當一份共享數據只能一個西安測繪給你寫數據,能夠多個線程讀數據。能夠選擇讀寫鎖,支持併發讀,獨佔寫,提升併發。微信

代碼以下:併發

import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;

public class ReadWrite {

    private ReadWrite() {
    }

    private static class singleFactory {
        private static final ReadWrite INSTANCE = new ReadWrite();
    }

    public static ReadWrite getInstance() {
        return singleFactory.INSTANCE;
    }

    /* 共享數據,只能一個線程寫數據,能夠多個線程讀數據 */
    private Object data = null;
    /* 建立一個讀寫鎖 */
    ReadWriteLock rwlock = new ReentrantReadWriteLock();

    /** * 讀數據,能夠多個線程同時讀, 因此上讀鎖便可 */
    public void get() {
        /* 上讀鎖 */
        rwlock.readLock().lock();

        try {
            System.out.println(Thread.currentThread().getName() + " 準備讀數據!");
            /* 休眠 */
            Thread.sleep((long) (Math.random() * 1000));
            System.out.println(Thread.currentThread().getName() + "讀出的數據爲 :" + data);
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            rwlock.readLock().unlock();
        }

    }

    /** * 寫數據,多個線程不能同時 寫 因此必須上寫鎖 * * @param data */
    public void put(Object data) {

        /* 上寫鎖 */
        rwlock.writeLock().lock();

        try {
            System.out.println(Thread.currentThread().getName() + " 準備寫數據!");
            /* 休眠 */
            Thread.sleep((long) (Math.random() * 1000));
            this.data = data;
            System.out.println(Thread.currentThread().getName() + " 寫入的數據: " + data);

        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            rwlock.writeLock().unlock();
        }
    }
}

複製代碼

單元測試dom

public class LockTest {
    public static void main(String[] args) {
        ReadWrite readWrite = ReadWrite.getInstance();


        for (int i = 0; i < 8; i++) {
            /* 建立並啓動8個讀線程 */
            new Thread(() -> readWrite.get()).start();

            /*建立8個寫線程*/
            new Thread(() -> readWrite.put(new Random().nextInt(8))).start();
        }
    }


}
複製代碼

運行結果:socket

Thread-0讀出的數據爲 :null
Thread-1 準備寫數據!
Thread-1 寫入的數據: 6
Thread-3 準備寫數據!
Thread-3 寫入的數據: 4
Thread-4 準備讀數據!
Thread-2 準備讀數據!
Thread-2讀出的數據爲 :4
Thread-4讀出的數據爲 :4
Thread-5 準備寫數據!
Thread-5 寫入的數據: 1
Thread-6 準備讀數據!
Thread-6讀出的數據爲 :1
Thread-7 準備寫數據!
Thread-7 寫入的數據: 6
Thread-8 準備讀數據!
Thread-8讀出的數據爲 :6
Thread-9 準備寫數據!
Thread-9 寫入的數據: 4
Thread-10 準備讀數據!
Thread-10讀出的數據爲 :4
Thread-11 準備寫數據!
Thread-11 寫入的數據: 4
Thread-12 準備讀數據!
Thread-12讀出的數據爲 :4
Thread-13 準備寫數據!
Thread-13 寫入的數據: 6
Thread-14 準備讀數據!
Thread-14讀出的數據爲 :6
Thread-15 準備寫數據!
Disconnected from the target VM, address: '127.0.0.1:55431', transport: 'socket'
Thread-15 寫入的數據: 0
複製代碼

這裏會有一個規律:獲取了寫鎖後數據必須從準備寫數據到寫入數據一鼓作氣,也就是原子操做,線程獨佔。高併發

而讀鎖的狀況下可有多個線程準備讀,多個線程同時讀出數據。單元測試

關注微信公衆號JavaStorm獲取最新文章。

JavaStorm
相關文章
相關標籤/搜索