读写锁 ReentrantReadWriteLock
ReentrantReadWriteLock 介绍
ReentrantReadWriteLock 是 Java 中的一个读写锁实现,它包含了两种锁:读锁和写锁。读锁是共享锁,多个线程可以同时获取读锁进行读操作,但是写锁是排他锁,只有一个线程可以获取写锁进行写操作。ReentrantReadWriteLock 提供了比单一锁更高效的并发性能,特别适用于读多写少的场景。
JDK 提供了 ReentrantReadWriteLock 读写锁,使用它可以加快效率,在某些不需要操作实例变量的方法中,完全可以使用读写锁 ReemtrantReadWriteLock 来提升该方法的运行速度。
定义:读写锁表示有两个锁,一个是读操作相关的锁,也称为共享锁;另一个是写操作相关的锁,也叫排他锁。
定义解读:也就是多个读锁之间不互斥,读锁与写锁互斥、写锁与写锁互斥。在没有线程 Thread 进行写入操作时,进行读取操作的多个 Thread 都可以获取读锁,而进行写入操作的 Thread 只有在获取写锁后才能进行写入操作。即多个 Thread 可以同时进行读取操作,但是同一时刻只允许一个 Thread 进行写入操作。
ReentrantReadWriteLock 的主要特点包括:
- 读写分离:读操作可以并发进行,写操作是排他的。
- 可重入性:读锁和写锁都支持重入。
- 公平性:可以选择是否公平地获取读写锁。
- 锁降级:可以将写锁降级为读锁,但不能将读锁升级为写锁。
- 条件变量:支持 Condition 条件变量。
ReentrantReadWriteLock 的主要方法包括:
- readLock():获取读锁。
- writeLock():获取写锁。
- readLock().lock():获取读锁。
- writeLock().lock():获取写锁。
- readLock().unlock():释放读锁。
- writeLock().unlock():释放写锁。
ReentrantReadWriteLock 的特点
性质 1 :可重入性。
ReentrantReadWriteLock 与 ReentrantLock 以及 synchronized 一样,都是可重入性锁,这里不会再多加赘述所得可重入性质,之前已经做过详细的讲解。
性质 2 :读写分离。
我们知道,对于一个数据,不管是几个线程同时读都不会出现任何问题,但是写就不一样了,几个线程对同一个数据进行更改就可能会出现数据不一致的问题,因此想出了一个方法就是对数据加锁,这时候出现了一个问题:
线程写数据的时候加锁是为了确保数据的准确性,但是线程读数据的时候再加锁就会大大降低效率,这时候怎么办呢?那就对写数据和读数据分开,加上两把不同的锁,不仅保证了正确性,还能提高效率。
性质 3 :可以锁降级,写锁降级为读锁。
线程获取写入锁后可以获取读取锁,然后释放写入锁,这样就从写入锁变成了读取锁,从而实现锁降级的特性。
性质 4 :不可锁升级。
线程获取读锁是不能直接升级为写入锁的。需要释放所有读取锁,才可获取写锁。
public class LockExample3 { private final Map<String, Data> map = new TreeMap<>(); private final ReentrantReadWriteLock lock = new ReentrantReadWriteLock(); private final Lock readLock = lock.readLock(); private final Lock writeLock = lock.writeLock(); public Data get(String key) { readLock.lock(); try { return map.get(key); } finally { readLock.unlock(); } } public Set<String> getAllKeys() { readLock.lock(); try { return map.keySet(); } finally { readLock.unlock(); } } public Data put(String key, Data value) { writeLock.lock(); try { return map.put(key, value); } finally { readLock.unlock(); } } class Data { } }
ReentrantReadWriteLock 读锁共享
我们之前说过,ReentrantReadWriteLock 之所以优秀,是因为读锁与写锁是分离的,当所有的线程都为读操作时,不会造成线程之间的互相阻塞,提升了效率,那么接下来,我们通过代码实例进行学习。
场景设计:
- 创建三个线程,线程名称分别为 t1,t2,t3,线程实现方式自行选择;
- 三个线程同时运行获取读锁,读锁成功后打印线程名和获取结果,并沉睡 2000 毫秒,便于观察其他线程是否可共享读锁;
- finally 模块中释放锁并打印线程名和释放结果;
- 运行程序,观察结果。
结果预期:三条线程能同时获取锁,因为读锁共享。
public class DemoTest { private ReentrantReadWriteLock lock = new ReentrantReadWriteLock();// 读写锁 private int i; public String readI() { try { lock.readLock().lock();// 占用读锁 System.out.println("threadName -> " + Thread.currentThread().getName() + " 占用读锁,i->" + i); Thread.sleep(2000); } catch (InterruptedException e) { } finally { System.out.println("threadName -> " + Thread.currentThread().getName() + " 释放读锁,i->" + i); lock.readLock().unlock();// 释放读锁 } return i + ""; } public static void main(String[] args) { final DemoTest demo1 = new DemoTest(); Runnable runnable = new Runnable() { @Override public void run() { demo1.readI(); } }; new Thread(runnable, "t1"). start(); new Thread(runnable, "t2"). start(); new Thread(runnable, "t3"). start(); } }
ReentrantReadWriteLock 读写互斥
当共享变量有写操作时,必须要对资源进行加锁,此时如果一个线程正在进行读操作,那么写操作的线程需要等待。同理,如果一个线程正在写操作,读操作的线程需要等待。
场景设计:细节操作不详细阐述,看示例代码即可。
- 创建两个线程,线程名称分别为 t1,t2;
- 线程 t1 进行读操作,获取到读锁之后,沉睡 5000 毫秒;
- 线程 t2 进行写操作;
- 开启 t1,1000 毫秒后开启 t2 线程;
- 运行程序,观察结果。
结果预期:线程 t1 获取了读锁,在沉睡的 5000 毫秒中,线程 t2 只能等待,不能获取到锁,因为读写互斥。
public class DemoTest { private ReentrantReadWriteLock lock = new ReentrantReadWriteLock();// 读写锁 private int i; public String readI() { try { lock.readLock().lock();// 占用读锁 System.out.println("threadName -> " + Thread.currentThread().getName() + " 占用读锁,i->" + i); Thread.sleep(5000); } catch (InterruptedException e) { } finally { System.out.println("threadName -> " + Thread.currentThread().getName() + " 释放读锁,i->" + i); lock.readLock().unlock();// 释放读锁 } return i + ""; } public void addI() { try { lock.writeLock().lock();// 占用写锁 System.out.println("threadName -> " + Thread.currentThread().getName() + " 占用写锁,i->" + i); i++; } finally { System.out.println("threadName -> " + Thread.currentThread().getName() + " 释放写锁,i->" + i); lock.writeLock().unlock();// 释放写锁 } } public static void main(String[] args) throws InterruptedException { final DemoTest demo1 = new DemoTest(); new Thread(new Runnable() { @Override public void run() { demo1.readI(); } }, "t1"). start(); Thread.sleep(1000); new Thread(new Runnable() { @Override public void run() { demo1.addI(); } }, "t2"). start(); } }
本文来自博客园,作者:黄橙,转载请注明原文链接:https://www.cnblogs.com/RedOrange/p/18056983
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 25岁的心里话
· 闲置电脑爆改个人服务器(超详细) #公网映射 #Vmware虚拟网络编辑器
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· 零经验选手,Compose 一天开发一款小游戏!
· 一起来玩mcp_server_sqlite,让AI帮你做增删改查!!