Java中的读写锁ReentrantReadWriteLock详解,存在一个小缺陷

简介: 【5月更文挑战第8天】Java中的读写锁ReentrantReadWriteLock详解,存在一个小缺陷

写在开头

最近是和java.util.concurrent.locks包下的同步类干上了,素有 并发根基 之称的concurrent包中全是精品,今天我们继续哈,今天学习的主题要由一个大厂常问的Java面试题开始:

小伙子,来说一说Java中的读写锁,你都用过哪些读写锁吧?

这个问题小伙伴们遇到了该如何回答呢?心里琢磨去吧,哈哈😄,不过build哥的回答要用从ReentrantReadWriteLock开始说起了,这个类也就是今天的主角,而它们同样是来自于java.util.concurrent.locks之下!

image.png

读写锁诞生的背景

在过去学习的过程中我们学过 synchronized、 ReentrantLock这种独占式锁,他们的好处是保证了线程的安全,缺点是同一时刻只能有一个线程持有锁,大大的影响了效率,而之前学过的Semaphore(信号量)这种呢,虽然支持同一时刻被多个线程获取,但它不能很好的保障线程安全性,我们需要的是一种效率高、安全性好的同步锁。

考虑到真正的生产生活中,对于数据的读取要比写入更为频繁,伟大的开发者们,将读数据的时候设置为共享锁,支持多个线程持有读锁,而在写的时候,考虑到线程安全,采用独占锁,同一时候仅允许一个线程持有写锁,在这种背景下读写锁应运而生!

读写锁:ReentrantReadWriteLock

ReentrantReadWriteLock是ReadWriteLock 接口的默认实现类,从名字可以看得出它也是一种具有可重入性的锁,同时也支持公平与非公平的配置,底层有两把锁,一把是 WriteLock (写锁),一把是 ReadLock(读锁) 。读锁是共享锁,写锁是独占锁。读锁可以被同时读,可以同时被多个线程持有,而写锁最多只能同时被一个线程持有,也是基于AQS实现的底层锁获取与释放逻辑。

image.png

内部构造

根据上面的构造图如果还没有搞清楚ReentrantReadWriteLock的底层构造的话,那我们跟入源码中取一探究竟吧!

【源码分析】

// 内部结构
private final ReentrantReadWriteLock.ReadLock readerLock;
private final ReentrantReadWriteLock.WriteLock writerLock;
final Sync sync;
/*1、用以继承AQS,获得AOS的特性,以及AQS的钩子函数*/
abstract static class Sync extends AbstractQueuedSynchronizer {
   
    // 具体实现
}
/*非公平模式,默认为这种模式*/
static final class NonfairSync extends Sync {
   
    // 具体实现
}
/*公平模式,通过构造方法参数设置*/
static final class FairSync extends Sync {
   
    // 具体实现
}
/*读锁,底层是共享锁*/
public static class ReadLock implements Lock, java.io.Serializable {
   
    private final Sync sync;
    protected ReadLock(ReentrantReadWriteLock lock) {
   
            sync = lock.sync;
    }
    // 具体实现
}
/*写锁,底层是独占锁*/
public static class WriteLock implements Lock, java.io.Serializable {
   
    private final Sync sync;
    protected WriteLock(ReentrantReadWriteLock lock) {
   
            sync = lock.sync;
    }
    // 具体实现
}

// 构造方法,初始化两个锁
public ReentrantReadWriteLock(boolean fair) {
   
    sync = fair ? new FairSync() : new NonfairSync();
    readerLock = new ReadLock(this);
    writerLock = new WriteLock(this);
}

// 获取读锁和写锁的方法
public ReentrantReadWriteLock.WriteLock writeLock() {
    return writerLock; }
public ReentrantReadWriteLock.ReadLock  readLock()  {
    return readerLock; }

上面为底层的主要构造内容,ReentrantReadWriteLock中共写了5个静态内部类,各有功效,在上面的注释中也有提及。

使用案例

那么这个读写锁如何使用呢?我们写一个小小的测试案例,也感受一下。

【测试案例】

public class Test {
   
    private final ReentrantReadWriteLock lock = new ReentrantReadWriteLock();
    private int data = 0;

    /**
     * 写方法
     * @param value
     */
    public void write(int value) {
   
        //注意,获取锁的操作要在try/finally外面
        lock.writeLock().lock(); // 获取写锁
        try {
   
            data = value;
            System.out.println("线程:"+Thread.currentThread().getName() + "写" + data);
        } finally {
   
            lock.writeLock().unlock(); // 释放写锁
        }
    }

    public void read() {
   
        lock.readLock().lock(); // 获取读锁
        try {
   
            System.out.println("线程:" + Thread.currentThread().getName() + "读" + data);
        } finally {
   
            lock.readLock().unlock(); // 释放读锁
        }
    }

    public static void main(String[] args) {
   
        Test test = new Test();
        // 创建读线程
        Thread readThread1 = new Thread(() -> {
   
            for (int i = 0; i < 5; i++) {
   
                test.read();
            }
        });

        Thread readThread2 = new Thread(() -> {
   
            for (int i = 0; i < 5; i++) {
   
                test.read();
            }
        });

        // 创建写线程
        Thread writeThread = new Thread(() -> {
   
            for (int i = 0; i < 5; i++) {
   
                test.write(i);
            }
        });

        readThread1.start();
        readThread2.start();
        writeThread.start();

        try {
   
            readThread1.join();
            readThread2.join();
            writeThread.join();
        } catch (InterruptedException e) {
   
            e.printStackTrace();
        }
    }
}

输出:

线程:Thread-10
线程:Thread-00
线程:Thread-10
线程:Thread-20
线程:Thread-21
线程:Thread-22
线程:Thread-23
线程:Thread-03
线程:Thread-13
线程:Thread-24
线程:Thread-04
线程:Thread-14
线程:Thread-04
线程:Thread-14
线程:Thread-04

通过输出内容,我们进一步得证,在ReentrantReadWriteLock在使用读锁时,可以支持多个线程获取读资源,而在调用写锁时,其他读线程和写线程均阻塞等待当前线程写完。

存在的问题

虽然ReentrantReadWriteLock优化了原有的独占锁对于程序读写的性能,但它仍然存在一个弊端,就是 “写饥饿” ,因为在写的时候,是独占模式,其他线程不能读也不能写,这时候若有大量的读操作的话,那这些线程也只能等待着,从而带来写饥饿。

那这个问题怎么解决呢?我们在下一篇StampedLock(锁王)的讲解中,进行解答哈,敬请期待!

结尾彩蛋

如果本篇博客对您有一定的帮助,大家记得留言+点赞+收藏呀。原创不易,转载请联系Build哥!

目录
相关文章
|
2月前
|
安全 Java 调度
Java编程时多线程操作单核服务器可以不加锁吗?
Java编程时多线程操作单核服务器可以不加锁吗?
44 2
|
10天前
|
缓存 Java
java中的公平锁、非公平锁、可重入锁、递归锁、自旋锁、独占锁和共享锁
本文介绍了几种常见的锁机制,包括公平锁与非公平锁、可重入锁与不可重入锁、自旋锁以及读写锁和互斥锁。公平锁按申请顺序分配锁,而非公平锁允许插队。可重入锁允许线程多次获取同一锁,避免死锁。自旋锁通过循环尝试获取锁,减少上下文切换开销。读写锁区分读锁和写锁,提高并发性能。文章还提供了相关代码示例,帮助理解这些锁的实现和使用场景。
java中的公平锁、非公平锁、可重入锁、递归锁、自旋锁、独占锁和共享锁
|
1月前
|
Java
Java 中锁的主要类型
【10月更文挑战第10天】
|
1月前
|
XML JavaScript Java
java与XML文件的读写
java与XML文件的读写
25 3
|
2月前
|
存储 缓存 安全
【Java面试题汇总】多线程、JUC、锁篇(2023版)
线程和进程的区别、CAS的ABA问题、AQS、哪些地方使用了CAS、怎么保证线程安全、线程同步方式、synchronized的用法及原理、Lock、volatile、线程的六个状态、ThreadLocal、线程通信方式、创建方式、两种创建线程池的方法、线程池设置合适的线程数、线程安全的集合?ConcurrentHashMap、JUC
【Java面试题汇总】多线程、JUC、锁篇(2023版)
|
2月前
|
算法 Java 关系型数据库
Java中到底有哪些锁
【9月更文挑战第24天】在Java中,锁主要分为乐观锁与悲观锁、自旋锁与自适应自旋锁、公平锁与非公平锁、可重入锁以及独享锁与共享锁。乐观锁适用于读多写少场景,通过版本号或CAS算法实现;悲观锁适用于写多读少场景,通过加锁保证数据一致性。自旋锁与自适应自旋锁通过循环等待减少线程挂起和恢复的开销,适用于锁持有时间短的场景。公平锁按请求顺序获取锁,适合等待敏感场景;非公平锁性能更高,适合频繁加解锁场景。可重入锁支持同一线程多次获取,避免死锁;独享锁与共享锁分别用于独占和并发读场景。
|
1月前
|
安全 Java 开发者
java的synchronized有几种加锁方式
Java的 `synchronized`通过上述三种加锁方式,为开发者提供了从粗粒度到细粒度的并发控制能力,满足了不同场景下的线程安全需求。合理选择加锁方式对于提升程序的并发性能和正确性至关重要,开发者应根据实际应用场景的特性和性能要求来决定使用哪种加锁策略。
16 0
|
1月前
|
Java 应用服务中间件 测试技术
Java21虚拟线程:我的锁去哪儿了?
【10月更文挑战第8天】
32 0
|
2月前
|
Java 数据库
JAVA并发编程-一文看懂全部锁机制
曾几何时,面试官问:java都有哪些锁?小白,一脸无辜:用过的有synchronized,其他不清楚。面试官:回去等通知! 今天我们庖丁解牛说说,各种锁有什么区别、什么场景可以用,通俗直白的分析,让小白再也不怕面试官八股文拷打。
|
2月前
|
安全 Java 开发者
Java并发编程中的锁机制解析
本文深入探讨了Java中用于管理多线程同步的关键工具——锁机制。通过分析synchronized关键字和ReentrantLock类等核心概念,揭示了它们在构建线程安全应用中的重要性。同时,文章还讨论了锁机制的高级特性,如公平性、类锁和对象锁的区别,以及锁的优化技术如锁粗化和锁消除。此外,指出了在高并发环境下锁竞争可能导致的问题,并提出了减少锁持有时间和使用无锁编程等策略来优化性能的建议。最后,强调了理解和正确使用Java锁机制对于开发高效、可靠并发应用程序的重要性。
28 3