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
相关文章
相关标签/搜索