java

关注公众号 jb51net

关闭
首页 > 软件编程 > java > Java读写锁ReentrantReadWriteLock

一文了解Java读写锁ReentrantReadWriteLock的使用

作者:JAVA旭阳

ReentrantReadWriteLock称为读写锁,它提供一个读锁,支持多个线程共享同一把锁。这篇文章主要讲解一下ReentrantReadWriteLock的使用和应用场景,感兴趣的可以了解一下

概述

ReentrantReadWriteLock不知道大家熟悉吗?其实在实际的项目中用的比较少,反正我所在的项目没有用到过。

ReentrantReadWriteLock称为读写锁,它提供一个读锁,支持多个线程共享同一把锁。它也提供了一把写锁,是独占锁,和其他读锁或者写锁互斥,表明只有一个线程能持有锁资源。通过两把锁的协同工作,能够最大化的提高读写的性能,特别是读多写少的场景,而往往大部分的场景都是读多写少的。

本文主要讲解ReentrantReadWriteLock的使用和应用场景。

ReentrantReadWriteLock介绍

ReentrantReadWriteLock实现了ReadWriteLock接口,可以获取到读锁(共享锁),写锁(独占锁)。同时,通过构造方法可以创建锁本身是公平锁还是非公锁。

读写锁机制:

 读锁写锁
读锁共享互斥
写锁互斥互斥

线程进入读锁的前提条件:

进入写锁的前提条件:

锁升级、降级机制:

我们知道ReentrantLock具备可重入的能力,即同一个线程多次获取锁,不引起阻塞,那么ReentrantReadWriteLock关于可重入性是怎么样的呢?

关于这个问题需要引入两个概念,锁升级,锁降级。

重入时锁升级不支持:持有读锁的情况下去获取写锁会导致获取写锁永久等待,需要先释放读,再去获得写

重入时锁降级支持:持有写锁的情况下去获取读锁,造成只有当前线程会持有读锁,因为写锁会互斥其他的锁

API介绍

构造方法:

常用API:

代码范式

加解锁格式

r.lock();
try {
    // 临界区
} finally {
	r.unlock();
}

锁降级

w.lock();
try {
    r.lock();// 降级为读锁, 释放写锁, 这样能够让其它线程读取缓存
    try {
        // ...
    } finally{
    	w.unlock();// 要在写锁释放之前获取读锁
    }
} finally{
	r.unlock();
}

实战案例

验证读读共享模式

@Test
    public void readReadMode() throws InterruptedException {
        ReentrantReadWriteLock rw = new ReentrantReadWriteLock();
        ReentrantReadWriteLock.ReadLock r = rw.readLock();
        ReentrantReadWriteLock.WriteLock w = rw.writeLock();

        Thread thread0 = new Thread(() -> {
            r.lock();
            try {
                Thread.sleep(1000);
                System.out.println("Thread 1 running " + new Date());
            } catch (InterruptedException e) {
                e.printStackTrace();
            } finally {
                r.unlock();
            }
        },"t1");

        Thread thread1 = new Thread(() -> {
            r.lock();
            try {
                Thread.sleep(1000);
                System.out.println("Thread 2 running " + new Date());
            } catch (InterruptedException e) {
                e.printStackTrace();
            } finally {
                r.unlock();
            }
        },"t2");

        thread0.start();
        thread1.start();

        thread0.join();
        thread1.join();
    }

运行结果:

两个线程同时运行,都获取到了读锁

验证读写互斥模式

@Test
    public void readWriteMode() throws InterruptedException {
        ReentrantReadWriteLock rw = new ReentrantReadWriteLock();
        ReentrantReadWriteLock.ReadLock r = rw.readLock();
        ReentrantReadWriteLock.WriteLock w = rw.writeLock();

        Thread thread0 = new Thread(() -> {
            r.lock();
            try {
                Thread.sleep(1000);
                System.out.println("Thread 1 running " + new Date());
            } catch (InterruptedException e) {
                e.printStackTrace();
            } finally {
                r.unlock();
            }
        },"t1");

        Thread thread1 = new Thread(() -> {
            w.lock();
            try {
                Thread.sleep(1000);
                System.out.println("Thread 2 running " + new Date());
            } catch (InterruptedException e) {
                e.printStackTrace();
            } finally {
                w.unlock();
            }
        },"t2");

        thread0.start();
        thread1.start();

        thread0.join();
        thread1.join();
    }

运行结果:

两个线程间隔1秒,互斥执行

真实缓存例子

什么场景下读多写少? 想必最先想到的就是缓存把,ReentrantReadWriteLock在缓存场景中就是一个很典型的应用。

缓存更新时,是先清缓存还是先更新数据库?

显而易见,通常情况下,先更新数据库,然后清空缓存。

public class GenericCachedDao {

    // 缓存对象,这里用jvm缓存
    Map<String, String> cache = new HashMap<>();
    // 读写锁
    ReadWriteLock readWriteLock = new ReentrantReadWriteLock();

    // 读取操作
    public String getData(String key) {
        // 加读锁,防止其他线程修改缓存
        readWriteLock.readLock().lock();
        try {
            String value = cache.get(key);
            // 如果缓存命中,返回
            if(value != null) {
                return value;
            }
        } finally {
            // 释放读锁
            readWriteLock.readLock().unlock();
        }

        //如果缓存没有命中,从数据库中加载
        readWriteLock.writeLock().lock();
        try {
            // 细节,为防止重复查询数据库, 再次验证
            // 因为get 方法上面部分是可能多个线程进来的, 可能已经向缓存填充了数据
            String value = cache.get(key);
            if(value == null) {
                // 这里可以改成从数据库查询
                value = "alvin";
                cache.put(key, value);
            }
            return value;
        } finally {
            readWriteLock.writeLock().unlock();
        }
    }

    // 更新数据
    public void updateData(String key, String value) {
        // 加写锁
        readWriteLock.writeLock().lock();
        try {
            // 更新操作TODO

            // 清空缓存
            cache.remove(key);
        } finally {
            readWriteLock.writeLock().unlock();
        }
    }
}

getData方法是读取操作,先加读锁,从缓存读取,如果没有命中,加写锁,此时其他线程就不能读取了,等写入成功后,释放读锁。

updateData方法是写操作,更新时加写锁,其他线程此时无法读取,然后清空缓存中的旧数据。

到此这篇关于一文了解Java读写锁ReentrantReadWriteLock的使用的文章就介绍到这了,更多相关Java读写锁ReentrantReadWriteLock内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

您可能感兴趣的文章:
阅读全文