欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  IT编程

ReentrantReadWriteLock读写锁

程序员文章站 2022-03-25 20:17:29
ReentrantLock是一个排他锁,同一时间只允许一个线程访问,而ReentrantReadWriteLock允许多个读线程同时访问,但不允许写线程和读线程、写线程和写线程同时访问。相对于排他锁,提高了并发性。在实际应用中,大部分情况下对共享数据(如缓存)的访问都是读操作远多于写操作,这时Ree... ......

概述

​ reentrantreadwritelock是lock的另一种实现方式,我们已经知道了reentrantlock是一个排他锁,同一时间只允许一个线程访问,而reentrantreadwritelock允许多个读线程同时访问,但不允许写线程和读线程、写线程和写线程同时访问。相对于排他锁,提高了并发性。在实际应用中,大部分情况下对共享数据(如缓存)的访问都是读操作远多于写操作,这时reentrantreadwritelock能够提供比排他锁更好的并发性和吞吐量。

​ 读写锁内部维护了两个锁,一个用于读操作,一个用于写操作。所有 readwritelock实现都必须保证 writelock操作的内存同步效果也要保持与相关 readlock的联系。也就是说,成功获取读锁的线程会看到写入锁之前版本所做的所有更新。

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();
        }
    }
}

单元测试

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();
        }
    }


}

运行结果:

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获取最新文章。ReentrantReadWriteLock读写锁