深入探索Java并发库(JUC)中的ReentrantReadWriteLock

简介: 深入探索Java并发库(JUC)中的ReentrantReadWriteLock

一、ReentrantReadWriteLock的基本概念

ReentrantReadWriteLock,即可重入的读写锁,它维护了两把锁:读锁和写锁。读锁允许多个线程同时持有,从而允许多个线程同时读取共享资源,提高了并发读取的效率。而写锁则是独占的,同一时间只能被一个线程持有,用于保护写入共享资源的操作。

需要注意的是,读锁和写锁是互斥的。当写锁被持有时,其他线程无法获取读锁或写锁,从而保证了写入操作的独占性。而当读锁被持有时,虽然其他线程可以继续获取读锁,但无法获取写锁,这保证了在读取过程中共享资源不会被修改。

二、ReentrantReadWriteLock的特性

  1. 可重入性:ReentrantReadWriteLock允许同一个线程多次获取同一个锁,无论是读锁还是写锁。这使得线程可以在持有锁的情况下进行递归调用或多次访问共享资源,而不会产生死锁。
  2. 公平性选择:ReentrantReadWriteLock可以在创建时选择是否是公平的。公平锁意味着锁的获取顺序将按照线程请求锁的顺序来,即遵循先来先服务的原则;而非公平锁则不保证按照顺序分配锁,可能会导致某些线程长时间得不到锁。
  3. 锁降级:ReentrantReadWriteLock支持锁降级操作,即线程可以先获取写锁,然后释放写锁并获取读锁。这在某些场景下非常有用,比如线程在修改共享资源后需要读取修改后的结果。

三、ReentrantReadWriteLock原理解读

ReentrantReadWriteLock的实现机制相对复杂,涉及多个内部类和同步控制。下面我将简要概述其核心实现机制,并提供一些关键部分的源码分析。

3.1 核心组件

  1. Sync:ReentrantReadWriteLock内部的一个抽象队列同步器(AQS),它继承自AbstractQueuedSynchronizer,用于实现锁的核心逻辑。
  2. ReadLock:读锁的实现,内部持有一个Sync对象,用于实现读锁的获取和释放。
  3. WriteLock:写锁的实现,同样内部持有一个Sync对象,用于实现写锁的获取和释放。

3.2 实现机制

  • 状态表示:Sync内部使用一个整型的state字段来表示锁的状态。高16位表示读锁的持有次数,低16位表示写锁的持有次数。
  • 写锁获取:当线程尝试获取写锁时,会调用Sync的tryAcquire方法。如果state不为0(即已经有读锁或写锁被持有),则获取失败。如果成功获取写锁,会将state的低16位加1,并设置当前线程为锁的持有者。
  • 读锁获取:当线程尝试获取读锁时,会调用Sync的trySharedAcquire方法。如果state的低16位不为0(即有写锁被持有),则获取失败。如果成功获取读锁,会将state的高16位加1,并可能设置当前线程为锁的持有者(如果是第一个获取读锁的线程)。
  • 锁释放:无论是读锁还是写锁,释放时都会调用Sync的tryRelease或trySharedRelease方法。释放写锁时,会将state的低16位减1;释放读锁时,会将state的高16位减1。如果完全释放了锁(state变为0),则会唤醒等待队列中的其他线程。
  • 公平性:ReentrantReadWriteLock在构造时可以指定是否为公平锁。公平锁会按照线程请求锁的顺序来分配锁,而非公平锁则可能会跳过等待队列中的某些线程直接获取锁。

3.3 源码骨架分析

ReentrantReadWriteLock的源码较长且复杂,这里给出部分关键代码辅助分析。

// ReentrantReadWriteLock的部分内部类定义
public class ReentrantReadWriteLock
    implements ReadWriteLock, java.io.Serializable {

    // 内部抽象类Sync继承自AbstractQueuedSynchronizer
    abstract static class Sync extends AbstractQueuedSynchronizer {
        // ... 省略其他方法和字段 ...

        // 尝试获取写锁的方法(由WriteLock调用)
        final boolean tryAcquire(int acquires) {
            // ... 省略实现细节 ...
        }

        // 尝试释放写锁的方法(由WriteLock调用)
        final boolean tryRelease(int releases) {
            // ... 省略实现细节 ...
        }

        // 尝试获取读锁的方法(由ReadLock调用)
        final int tryAcquireShared(int acquires) {
            // ... 省略实现细节 ...
        }

        // 尝试释放读锁的方法(由ReadLock调用)
        final boolean tryReleaseShared(int releases) {
            // ... 省略实现细节 ...
        }

        // 判断是否处于读锁状态
        final boolean isHeldExclusively() {
            // ... 省略实现细节 ...
        }
    }

    // 非公平锁的实现类NonfairSync继承自Sync
    static final class NonfairSync extends Sync {
        // ... 省略其他方法和字段 ...
    }

    // 公平锁的实现类FairSync继承自Sync
    static final class FairSync extends Sync {
        // ... 省略其他方法和字段 ...
    }

    // 读锁的实现类ReadLock实现Lock接口
    public static class ReadLock implements Lock, java.io.Serializable {
        // ... 省略其他方法和字段 ...

        // 读锁的lock方法实现
        public void lock() {
            sync.acquireShared(1);
        }

        // 读锁的unlock方法实现
        public void unlock() {
            sync.releaseShared(1);
        }
    }

    // 写锁的实现类WriteLock实现Lock接口
    public static class WriteLock implements Lock, java.io.Serializable {
        // ... 省略其他方法和字段 ...

        // 写锁的lock方法实现
        public void lock() {
            sync.acquire(1);
        }

        // 写锁的unlock方法实现
        public void unlock() {
            sync.release(1);
        }
    }

    // ... 省略其他方法和字段 ...
}

ReentrantReadWriteLock定义了两个内部类ReadLock和WriteLock来实现读锁和写锁的功能。它们内部都持有一个Sync对象,用于实际的锁操作。Sync是一个继承自AbstractQueuedSynchronizer的抽象类,它实现了锁的核心逻辑。具体的公平性和非公平性锁的实现则通过FairSync和NonfairSync两个类来完成,它们分别继承自Sync。

四、ReentrantReadWriteLock的使用

下面是一个使用ReentrantReadWriteLock的代码,其中包含一个共享资源(一个简单的计数器),多个读线程和写线程将并发地访问这个资源。读线程只会读取计数器的值,而写线程会修改计数器的值。

import java.util.concurrent.locks.ReentrantReadWriteLock;

public class ReadWriteLockExample {

    // 共享资源:一个简单的计数器
    private int counter = 0;

    // 创建一个ReentrantReadWriteLock对象
    private final ReentrantReadWriteLock readWriteLock = new ReentrantReadWriteLock();

    // 获取读锁
    private final ReentrantReadWriteLock.ReadLock readLock = readWriteLock.readLock();

    // 获取写锁
    private final ReentrantReadWriteLock.WriteLock writeLock = readWriteLock.writeLock();

    // 读操作:增加读计数
    public int readCounter() {
        readLock.lock(); // 加读锁
        try {
            // 模拟读操作的耗时
            Thread.sleep(10);
            return counter;
        } catch (InterruptedException e) {
            e.printStackTrace();
            return -1;
        } finally {
            readLock.unlock(); // 释放读锁
        }
    }

    // 写操作:增加计数器的值
    public void incrementCounter() {
        writeLock.lock(); // 加写锁
        try {
            // 模拟写操作的耗时
            Thread.sleep(10);
            counter++;
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            writeLock.unlock(); // 释放写锁
        }
    }

    // 读线程类
    class ReaderThread extends Thread {
        @Override
        public void run() {
            for (int i = 0; i < 5; i++) {
                System.out.println("读线程" + Thread.currentThread().getId() + "读取计数器值:" + readCounter());
            }
        }
    }

    // 写线程类
    class WriterThread extends Thread {
        @Override
        public void run() {
            for (int i = 0; i < 5; i++) {
                incrementCounter();
                System.out.println("写线程" + Thread.currentThread().getId() + "增加计数器值");
            }
        }
    }

    // 测试方法
    public static void main(String[] args) {
        ReadWriteLockExample example = new ReadWriteLockExample();

        // 启动多个读线程和写线程
        for (int i = 0; i < 3; i++) {
            new Thread(example.new ReaderThread()).start();
            new Thread(example.new WriterThread()).start();
        }
    }
}

我们定义了一个ReadWriteLockExample类,它包含一个计数器counter,一个ReentrantReadWriteLock对象以及对应的读锁和写锁。我们定义了两个方法readCounter和incrementCounter来分别执行读操作和写操作,并在执行前后分别加锁和释放锁。


我们还定义了两个内部类ReaderThread和WriterThread来分别代表读线程和写线程。在main方法中,我们创建了多个读线程和写线程并启动它们,以模拟并发访问共享资源的场景。

五、ReentrantReadWriteLock的使用注意事项

  1. 避免锁升级:与锁降级相反,锁升级(从读锁升级到写锁)是不被支持的。如果线程已经持有了读锁并试图获取写锁,将会导致死锁。因此,在设计并发程序时应尽量避免这种情况的发生。
  2. 注意读写锁的互斥性:虽然多个线程可以同时持有读锁,但写锁是独占的。在设计并发程序时,应充分考虑读写锁的互斥性,避免因为不恰当的锁使用导致并发性能下降或死锁等问题。
  3. 选择合适的公平性策略:根据实际需求选择合适的公平性策略是非常重要的。公平锁可以确保所有线程都有机会获取锁,避免了某些线程长时间得不到锁的情况;但非公平锁在某些场景下可能具有更高的并发性能。

总结

ReentrantReadWriteLock是Java并发库中一种非常实用的读写锁实现,它允许多个线程同时读取共享资源,提高了程序的并发性能。在使用时需要注意正确获取和释放锁,避免死锁等问题;同时根据实际需求选择合适的公平性策略也是非常重要的。通过深入了解和合理使用ReentrantReadWriteLock,我们可以编写出更高效、更安全的并发程序。

相关文章
|
2月前
|
安全 Java API
JAVA并发编程JUC包之CAS原理
在JDK 1.5之后,Java API引入了`java.util.concurrent`包(简称JUC包),提供了多种并发工具类,如原子类`AtomicXX`、线程池`Executors`、信号量`Semaphore`、阻塞队列等。这些工具类简化了并发编程的复杂度。原子类`Atomic`尤其重要,它提供了线程安全的变量更新方法,支持整型、长整型、布尔型、数组及对象属性的原子修改。结合`volatile`关键字,可以实现多线程环境下共享变量的安全修改。
|
17天前
|
存储 设计模式 分布式计算
Java中的多线程编程:并发与并行的深度解析####
在当今软件开发领域,多线程编程已成为提升应用性能、响应速度及资源利用率的关键手段之一。本文将深入探讨Java平台上的多线程机制,从基础概念到高级应用,全面解析并发与并行编程的核心理念、实现方式及其在实际项目中的应用策略。不同于常规摘要的简洁概述,本文旨在通过详尽的技术剖析,为读者构建一个系统化的多线程知识框架,辅以生动实例,让抽象概念具体化,复杂问题简单化。 ####
|
1月前
|
存储 消息中间件 安全
JUC组件实战:实现RRPC(Java与硬件通过MQTT的同步通信)
【10月更文挑战第9天】本文介绍了如何利用JUC组件实现Java服务与硬件通过MQTT的同步通信(RRPC)。通过模拟MQTT通信流程,使用`LinkedBlockingQueue`作为消息队列,详细讲解了消息发送、接收及响应的同步处理机制,包括任务超时处理和内存泄漏的预防措施。文中还提供了具体的类设计和方法实现,帮助理解同步通信的内部工作原理。
JUC组件实战:实现RRPC(Java与硬件通过MQTT的同步通信)
|
19天前
|
Java API Apache
|
22天前
|
Java 数据库连接 数据库
如何构建高效稳定的Java数据库连接池,涵盖连接池配置、并发控制和异常处理等方面
本文介绍了如何构建高效稳定的Java数据库连接池,涵盖连接池配置、并发控制和异常处理等方面。通过合理配置初始连接数、最大连接数和空闲连接超时时间,确保系统性能和稳定性。文章还探讨了同步阻塞、异步回调和信号量等并发控制策略,并提供了异常处理的最佳实践。最后,给出了一个简单的连接池示例代码,并推荐使用成熟的连接池框架(如HikariCP、C3P0)以简化开发。
45 2
|
1月前
|
JSON JavaScript Java
在Java中处理JSON数据:Jackson与Gson库比较
本文介绍了JSON数据交换格式及其在Java中的应用,重点探讨了两个强大的JSON处理库——Jackson和Gson。文章详细讲解了Jackson库的核心功能,包括数据绑定、流式API和树模型,并通过示例演示了如何使用Jackson进行JSON解析和生成。最后,作者分享了一些实用的代码片段和使用技巧,帮助读者更好地理解和应用这些工具。
在Java中处理JSON数据:Jackson与Gson库比较
|
1月前
|
Java
【编程进阶知识】揭秘Java多线程:并发与顺序编程的奥秘
本文介绍了Java多线程编程的基础,通过对比顺序执行和并发执行的方式,展示了如何使用`run`方法和`start`方法来控制线程的执行模式。文章通过具体示例详细解析了两者的异同及应用场景,帮助读者更好地理解和运用多线程技术。
29 1
|
2月前
|
存储 缓存 安全
【Java面试题汇总】多线程、JUC、锁篇(2023版)
线程和进程的区别、CAS的ABA问题、AQS、哪些地方使用了CAS、怎么保证线程安全、线程同步方式、synchronized的用法及原理、Lock、volatile、线程的六个状态、ThreadLocal、线程通信方式、创建方式、两种创建线程池的方法、线程池设置合适的线程数、线程安全的集合?ConcurrentHashMap、JUC
【Java面试题汇总】多线程、JUC、锁篇(2023版)
|
2月前
|
Java API 容器
JAVA并发编程系列(10)Condition条件队列-并发协作者
本文通过一线大厂面试真题,模拟消费者-生产者的场景,通过简洁的代码演示,帮助读者快速理解并复用。文章还详细解释了Condition与Object.wait()、notify()的区别,并探讨了Condition的核心原理及其实现机制。
|
1月前
|
JSON Java 数据格式
Java Jackson-jr库使用介绍
Jackson-jr是专为资源受限环境设计的轻量级JSON处理库,适用于微服务、移动应用及嵌入式系统。它通过牺牲部分高级功能实现了更小体积和更快启动速度,非常适合对库大小敏感的项目。本文将介绍如何使用Jackson-jr进行JSON序列化与反序列化,并演示处理嵌套对象与数组的方法。此外,还介绍了自定义序列化与反序列化的技巧以及性能与功能的权衡。通过示例代码,展示了Jackson-jr在常见任务中的高效与灵活性。
28 0
下一篇
无影云桌面