AQS之ReentrantReadWriteLock

本文详细介绍了AQS中的ReentrantReadWriteLock,包括其适用场景、锁降级机制如何确保数据可见性,以及读写状态的设计和获取释放过程。重点强调了公平与非公平策略在读写锁中的应用。

AQS之ReentrantReadWriteLock

一. 归纳总结

  1. ReentrantReadWriteLock适合读多写少的场景。是可重入的读写锁实现类。其中, 写锁是独占的,读锁是共享的。

  2. 支持锁降级(持有写锁、获取读锁,最后释放写锁的过程)

  • 锁降级可以帮助我们拿到当前线程修改后的结果而不被其他线程所破坏,防止更新丢失。
  • 可以保证数据的可见性,如果当前线程不获取读锁而是直接释放写锁,假设此刻另一个线程(记作线程T)获取了写锁并修改了数据,那么当前线程无法感知线程T的数据更新。如果当前线程获取读锁,即遵循锁降级的步骤,则线程T将会被阻塞,直到当前线程使用数据并释放读锁之后,线程T才能获取写锁进行数据更新。
  1. 不支持锁升级(持有读锁、获取写锁,最后释放读锁的过程)
    目的也是保证数据可见性,如果读锁已被多个线程获取,其中任意线程成功获取了写锁并更新了数据,则其更新对其他获取到读锁的线程是不可见的。

二.读写状态的设计

读写锁对于同步状态的实现是在一个整形变量上通过“按位切割使用”:将变量切割成两部分,高16位表示读,低16位表示写。
在这里插入图片描述
假设当前同步状态值为S,get和set的操作如下:

1、获取写状态:

S&0x0000FFFF:将高16位全部抹去

2、获取读状态:

S>>>16:无符号补0,右移16位

3、写状态加1:

 S+1

4、读状态加1:

S+(1<<16)即S + 0x00010000

在代码层面的判断中,同步状态S不等于0时,当写状态(S&0x0000FFFF)等于0时,则读状态(S>>>16)大于0,即读锁已被获取。

三.写锁的获取与释放

3.1 写锁的获取

  • 写锁是一个支持重进入的排它锁。如果当前线程已经获取了写锁,则增加写状态。如果当前线程在获取写锁时,读锁已经被获取(读状态不为0)或者该线程不是已经获取写锁的线程, 则当前线程进入等待状态。
  • 写锁的获取是通过重写AQS中的tryAcquire方法实现的。
protected final boolean tryAcquire(int acquires) {
    //当前线程
    Thread current = Thread.currentThread();
    //获取state状态   存在读锁或者写锁,状态就不为0
    int c = getState();
    //获取写锁的重入数
    int w = exclusiveCount(c);
    //当前同步状态state != 0,说明已经有其他线程获取了读锁或写锁
    if (c != 0) {
        // c!=0 && w==0 表示存在读锁
        // 当前存在读锁或者写锁已经被其他写线程获取,则写锁获取失败
        if (w == 0 || current != getExclusiveOwnerThread())
            return false;
        // 超出最大范围  65535
        if (w + exclusiveCount(acquires) > MAX_COUNT)
            throw new Error("Maximum lock count exceeded");
        //同步state状态
        setState(c + acquires);
        return true;
    }
    // writerShouldBlock有公平与非公平的实现, 非公平返回false,会尝试通过cas加锁
    //c==0 写锁未被任何线程获取,当前线程是否阻塞或者cas尝试获取锁 
    if (writerShouldBlock() ||
        !compareAndSetState(c, c + acquires))
        return false;

    //设置写锁为当前线程所有
    setExclusiveOwnerThread(current);
    return true;

源码分析:

  • 读写互斥
  • 写写互斥
  • 写锁支持同一个线程重入
  • writerShouldBlock写锁是否阻塞实现取决公平与非公平的策略(FairSync和NonfairSync)
    在这里插入图片描述

3.2 读锁的获取

实现共享式同步组件的同步语义需要通过重写AQS的tryAcquireShared方法和tryReleaseShared方法。读锁的获取实现方法为:

protected final int tryAcquireShared(int unused) {
    Thread current = Thread.currentThread();
    int c = getState();
    // 如果写锁已经被获取并且获取写锁的线程不是当前线程,当前线程获取读锁失败返回-1   判断锁降级
    if (exclusiveCount(c) != 0 &&
        getExclusiveOwnerThread() != current)
        return -1;
    //计算出读锁的数量
    int r = sharedCount(c);
    /**
    * 读锁是否阻塞    readerShouldBlock()公平与非公平的实现
    * r < MAX_COUNT: 持有读锁的线程小于最大数(65535)
    *  compareAndSetState(c, c + SHARED_UNIT) cas设置获取读锁线程的数量
    */
    if (!readerShouldBlock() &&
        r < MAX_COUNT &&
        compareAndSetState(c, c + SHARED_UNIT)) {   //当前线程获取读锁
        
        if (r == 0) {  //设置第一个获取读锁的线程
            firstReader = current; 
            firstReaderHoldCount = 1;  //设置第一个获取读锁线程的重入数
        } else if (firstReader == current) { // 表示第一个获取读锁的线程重入
            firstReaderHoldCount++;
        } else { // 非第一个获取读锁的线程
            HoldCounter rh = cachedHoldCounter;
            if (rh == null || rh.tid != getThreadId(current))
                cachedHoldCounter = rh = readHolds.get();
            else if (rh.count == 0)
                readHolds.set(rh);
            rh.count++;  //记录其他获取读锁的线程的重入次数
        }
        return 1;
    }
    // 尝试通过自旋的方式获取读锁,实现了重入逻辑
    return fullTryAcquireShared(current);

源码分析:

  • 读锁共享,读读不互斥
  • 读锁可重入,每个获取读锁的线程都会记录对应的重入数
  • 读写互斥,锁降级场景除外
  • 支持锁降级,持有写锁的线程,可以获取读锁,但是后续要记得把读锁和写锁读释放
  • readerShouldBlock读锁是否阻塞实现取决公平与非公平的策略(FairSync和NonfairSync)
    在这里插入图片描述
<think>好的,用户的问题是关于AQS的。首先需要明确用户的具体需求是什么。AQS可能指多个领域,比如Java中的AbstractQueuedSynchronizer,或者空气质量系统(Air Quality System),还有可能涉及其他缩写。根据常见的编程问题,推测用户可能是在询问Java并发框架中的AQS。 接下来要调用网络搜索工具,查找AQS的相关信息。主要关注Java中的AbstractQueuedSynchronizer,确认其作用、原理、使用场景等。同时需要整理搜索结果,确保信息的准确性和实用性。 在整合信息时,需要将AQS的基本概念、核心机制、应用场景分点说明,避免使用步骤词汇,保持每个部分独立。同时注意代码和公式的格式要求,确保正确使用行内公式和代码块。 需要检查是否遗漏其他可能的AQS含义,比如空气质量系统,但根据编程上下文,优先处理Java相关的解释。如果搜索结果中有其他含义,可能需要简要提及,但重点放在Java部分。 最后生成相关问题部分,需基于用户的问题和回答内容,提出三个相关的问题,比如实现原理、使用场景、与其他锁的关系等。确保问题符合格式要求,使用正确的标题和编号。</think>AQS(AbstractQueuedSynchronizer)是Java并发包中构建锁和同步器的核心框架,采用CLH队列变体实现线程阻塞管理。其核心机制基于**state**状态变量和**FIFO双向队列**实现资源获取/释放的线程调度。 **核心原理** - **状态管理**:通过volatile修饰的int类型`state`字段表示资源状态,子类通过`getState()`/`setState()`/`compareAndSetState()`进行原子操作。例如`ReentrantLock`用state=0表示未锁定,state>0表示锁定次数 - **队列节点**:队列节点包含`waitStatus`字段,包含以下状态: - `CANCELLED(1)`:线程已取消 - `SIGNAL(-1)`:后继节点需要唤醒 - `CONDITION(-2)`:处于条件等待队列 - `PROPAGATE(-3)`:共享模式下需要传播唤醒 **实现模式** ```java // 自定义同步器模板 class CustomSync extends AbstractQueuedSynchronizer { protected boolean tryAcquire(int acquires) { final Thread current = Thread.currentThread(); int c = getState(); if (c == 0) { if (compareAndSetState(0, acquires)) { setExclusiveOwnerThread(current); return true; } } return false; } } ``` **典型应用** - `ReentrantLock`:可重入排他锁 - `Semaphore`:信号量控制 - `CountDownLatch`:倒计时门闩 - `ReentrantReadWriteLock`:读写锁分离 **资源获取公式** $$资源状态 = \begin{cases} 0 & \text{可用状态} \\ n & \text{被占用状态(n表示重入次数/资源数量)} \end{cases}$$ **锁公平性实现** 公平锁通过`hasQueuedPredecessors()`方法检查队列中是否存在等待时间更长的线程,非公平锁直接尝试CAS抢锁: ```java final void lock() { if (compareAndSetState(0, 1)) // 非公平锁直接尝试抢锁 setExclusiveOwnerThread(Thread.currentThread()); else acquire(1); } ```
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值