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

目录
  • 概述
  • ReentrantReadWriteLock介绍
  • 实战案例
    • 验证读读共享模式
    • 验证读写互斥模式
    • 真实缓存例子

概述

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

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

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

ReentrantReadWriteLock介绍

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

读写锁机制:

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

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

  • 没有其他线程的写锁
  • 没有写请求,或者有写请求但调用线程和持有锁的线程是同一个线程

进入写锁的前提条件:

  • 没有其他线程的读锁
  • 没有其他线程的写锁

锁升级、降级机制:

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

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

  • 锁升级:从读锁变成写锁。
  • 锁降级:从写锁变成读锁;

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

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

API介绍

构造方法:

  • public ReentrantReadWriteLock():默认构造方法,非公平锁
  • public ReentrantReadWriteLock(boolean fair):true 为公平锁

常用API:

  • public ReentrantReadWriteLock.ReadLock readLock():返回读锁
  • public ReentrantReadWriteLock.WriteLock writeLock():返回写锁
  • public void lock():加锁
  • public void unlock():解锁
  • public boolean tryLock():尝试获取锁

代码范式

加解锁格式

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内容请搜索我们以前的文章或继续浏览下面的相关文章希望大家以后多多支持我们!

(0)

相关推荐

  • ReentrantReadWriteLock 读写锁分析总结

    目录 一.读写锁简介 二.读写锁使用 ReadWriteLock 接口 使用例子 三.锁的降级 锁降级的使用示例 四.ReentranReadWriteLock 结构 方法结构设计 读写状态设计 五.源码分析 写锁的加锁 写锁的释放 读锁的获取 读锁的释放 一.读写锁简介 现实中有这样一种场景:对共享资源有读和写的操作,且写操作没有读操作那么频繁(读多写少).在没有写操作的时候,多个线程同时读一个资源没有任何问题,所以应该允许多个线程同时读取共享资源(读读可以并发):但是如果一个线程想去写这些共

  • 详解Java ReentrantReadWriteLock读写锁的原理与实现

    目录 概述 原理概述 加锁原理 图解过程 源码解析 解锁原理 图解过程 源码解析 概述 ReentrantReadWriteLock读写锁是使用AQS的集大成者,用了独占模式和共享模式.本文和大家一起理解下ReentrantReadWriteLock读写锁的实现原理.在这之前建议大家阅读下下面3篇关联文章: 深入浅出理解Java并发AQS的独占锁模式 深入浅出理解Java并发AQS的共享锁模式 通俗易懂读写锁ReentrantReadWriteLock的使用 原理概述 上图是ReentrantR

  • Java多线程之ReentrantReadWriteLock源码解析

    一.介绍 1.1 ReentrantReadWriteLock ReentrantReadWriteLock 是一个读写锁,允许多个读或者一个写线程在执行. 内部的 Sync 继承自 AQS,这个 Sync 包含一个共享读锁 ReadLock 和一个独占写锁 WriteLock. 该锁可以设置公平和非公平,默认非公平. 一个持有写锁的线程可以获取读锁.如果该线程先持有写锁,再持有读锁并释放写锁,称为锁降级. WriteLock支持Condition并且与ReentrantLock语义一致,而Re

  • Java 多线程并发 ReentrantReadWriteLock详情

    目录 前言 ReadWriteLock ReentrantReadWriteLock 源码分析 类关系 Sync HoldCounter ThreadLocalHoldCounter 属性 构造方法 核心方法 锁的计数方法 读写锁阻塞检查方法 公平策略实现 FairSync 和非公平策略实现 NonfairSync NonfairSync 非公平策略 FairSync 公平策略 Release 和 Acquire 方法组 ReadLock WriteLock 读写锁降级 总结 前言 Reentr

  • Java多线程 ReentrantReadWriteLock原理及实例详解

    读写锁ReentrantReadWriteLock概述 读写锁ReentrantReadWriteLock,使用它比ReentrantLock效率更高. 读写锁表示两个锁,一个是读操作相关的锁,称为共享锁:另一个是写操作相关的锁,称为排他锁. 1.读和读之间不互斥,因为读操作不会有线程安全问题 2.写和写之间互斥,避免一个写操作影响另外一个写操作,引发线程安全问题 3.读和写之间互斥,避免读操作的时候写操作修改了内容,引发线程安全问题 多个Thread可以同时进行读取操作,但是同一时刻只允许一个

  • Java多线程读写锁ReentrantReadWriteLock类详解

    目录 ReentrantReadWriteLock 读读共享 写写互斥 读写互斥 源码分析 写锁的获取与释放 读锁的获取与释放 参考文献 真实的多线程业务开发中,最常用到的逻辑就是数据的读写,ReentrantLock虽然具有完全互斥排他的效果(即同一时间只有一个线程正在执行lock后面的任务),这样做虽然保证了实例变量的线程安全性,但效率却是非常低下的.所以在JDK中提供了一种读写锁ReentrantReadWriteLock类,使用它可以加快运行效率. 读写锁表示两个锁,一个是读操作相关的锁

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

    目录 概述 ReentrantReadWriteLock介绍 实战案例 验证读读共享模式 验证读写互斥模式 真实缓存例子 概述 ReentrantReadWriteLock不知道大家熟悉吗?其实在实际的项目中用的比较少,反正我所在的项目没有用到过. ReentrantReadWriteLock称为读写锁,它提供一个读锁,支持多个线程共享同一把锁.它也提供了一把写锁,是独占锁,和其他读锁或者写锁互斥,表明只有一个线程能持有锁资源.通过两把锁的协同工作,能够最大化的提高读写的性能,特别是读多写少的场

  • Java 读写锁实现原理浅析

    最近做的一个小项目中有这样的需求:整个项目有一份config.json保存着项目的一些配置,是存储在本地文件的一个资源,并且应用中存在读写(读>>写)更新问题.既然读写并发操作,那么就涉及到操作互斥,这里自然想到了读写锁,本文对读写锁方面的知识做个梳理. 为什么需要读写锁? 与传统锁不同的是读写锁的规则是可以共享读,但只能一个写,总结起来为:读读不互斥,读写互斥,写写互斥,而一般的独占锁是:读读互斥,读写互斥,写写互斥,而场景中往往读远远大于写,读写锁就是为了这种优化而创建出来的一种机制. 注

  • Java 读写锁源码分析

    前言 在实际项目中,比如我们有一个共享资源文件,我们程序会会同时并发的去读.写这个共享资源文件,那怎么能保证在高并发场景下安全.高效读写呢?OK,看了下文便知 提示:以下是本篇文章正文内容,案例仅供参考 一.技术介绍 1.ReentranReadWriteLock是什么? ReadWriteLock提供了readLock和writeLock两种锁的操作机制,一个是读锁,一个是写锁,而它的实现类就是ReentranReadWriteLock 读锁可以在没有写锁的时候被多个线程同时持有,写锁是独占的

  • java 读写锁的使用及它的优点

    目录 1.读写锁使用 1.1 读读不互斥 1.2 读写互斥 1.3 写写互斥 2.优点分析 3.适用场景 总结 前言: 读写锁(Readers-Writer Lock)顾名思义是一把锁分为两部分:读锁和写锁,其中读锁允许多个线程同时获得,因为读操作本身是线程安全的,而写锁则是互斥锁,不允许多个线程同时获得写锁,并且写操作和读操作也是互斥的.总结来说,读写锁的特点是:读读不互斥.读写互斥.写写互斥. 1.读写锁使用 在 Java 语言中,读写锁是使用 ReentrantReadWriteLock

  • Java读写锁ReadWriteLock原理与应用场景详解

    Java并发编程提供了读写锁,主要用于读多写少的场景 什么是读写锁? 读写锁并不是JAVA所特有的读写锁(Readers-Writer Lock)顾名思义是一把锁分为两部分:读锁和写锁,其中读锁允许多个线程同时获得,因为读操作本身是线程安全的,而写锁则是互斥锁,不允许多个线程同时获得写锁,并且写操作和读操作也是互斥的. 所谓的读写锁(Readers-Writer Lock),顾名思义就是将一个锁拆分为读锁和写锁两个锁. 其中读锁允许多个线程同时获得,而写锁则是互斥锁,不允许多个线程同时获得写锁,

  • Java并发编程之重入锁与读写锁

    重入锁 重入锁,顾名思义,就是支持重进入的锁,它表示该锁能够支持一个线程对资源的重复加锁.重进入是指任意线程在获取到锁之后能够再次获取该锁而不会被锁阻塞,该特性的实现需要解决以下两个问题. 1.线程再次获取锁.锁需要去识别获取锁的线程是否为当前占据锁的线程,如果是,则再次成功获取. 2.锁的最终释放.线程重复n次获取了锁,随后在第n次释放该锁后,其他线程能够获取到该锁.锁的最终释放要求锁对于获取进行计数自增,计数表示当前锁被重复获取的次数,而锁被释放时,计数自减,当计数等于0时表示锁已经成功释放

  • Java多线程编程中线程锁与读写锁的使用示例

    线程锁Lock Lock  相当于 当前对象的 Synchronized import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantLock; /* * Lock lock = new ReentrantLock(); * lock.lock(); lock.unLock(); * 类似于 synchronized,但不能与synchronized 混用 */ public class L

  • java并发编程中ReentrantLock可重入读写锁

    目录 一.ReentrantLock可重入锁 二.ReentrantReadWriteLock读写锁 三.读锁之间不互斥 一.ReentrantLock可重入锁 可重入锁ReentrantLock 是一个互斥锁,即同一时间只有一个线程能够获取锁定资源,执行锁定范围内的代码.这一点与synchronized 关键字十分相似.其基本用法代码如下: Lock lock = new ReentrantLock(); //实例化锁 //lock.lock(); //上锁 boolean locked =

随机推荐