AQS原理与源码剖析(结合ReentrantLock源码)

本文深入剖析AQS(AbstractQueuedSynchronizer)的原理,结合ReentrantLock源码,探讨AQS的数据结构,如同步队列和条件队列,以及核心变量state和waitStatus的维护。详细阐述了AQS的获取和释放锁的流程,并介绍了条件队列的相关操作,如await、signal和signalAll。同时,分析了ReentrantLock的公平锁和非公平锁实现策略。
摘要由CSDN通过智能技术生成

AQS原理与源码剖析(结合ReentrantLock源码)

原文地址:http://jachindo.top:8090/archives/aqs%E5%8E%9F%E7%90%86%E4%B8%8E%E6%BA%90%E7%A0%81%E5%89%96%E6%9E%90%E7%BB%93%E5%90%88reentrantlock%E6%BA%90%E7%A0%81

介绍:AQS是一种提供了原子式管理同步状态、阻塞和唤醒线程功能以及队列模型的简单框架


2. AQS数据结构

§ 队列概述

AQS包含两种队列:同步队列和条件队列,实现都如下:

image-20191217114957123

维护一个由双向链表实现的等待队列,链表元素被封装为一个Node对象(其中包含线程信息),这些节点都尝试通过CAS获取/修改state

节点的详细信息如下:

注意:head节点(队头)是一个虚节点(thread字段为null),仅保留waitStatus属性供后继节点做相应的判断。它代表其中的线程正在工作。

image-20191217115057708


§ 同步队列属性

当多个线程都来请求锁时,某一时刻有且只有一个线程能够获得锁(排它锁),那么剩余获取不到锁的线程,都会到同步队列中去排队并阻塞自己,当有线程主动释放锁时,就会从同步队列头开始释放一个排队的线程,让线程重新去竞争锁。

所以同步队列的主要作用是***阻塞获取不到锁的线程,并在适当时机释放这些线程。***

同步队列底层数据结构是个双向链表,我们从源码中可以看到链表的头尾,如下:

// 同步队列的头。
private transient volatile Node head;

// 同步队列的尾
private transient volatile Node tail;

§ 条件队列属性

条件队列和同步队列的功能一样,管理获取不到锁的线程,底层数据结构也是链表队列,但条件队列不直接和锁打交道,但常常和锁配合使用,是一定的场景下,对锁功能的一种补充。

条件队列的属性如下:

// 条件队列,从属性上可以看出是链表结构
public class ConditionObject implements Condition, java.io.Serializable {
   
    private static final long serialVersionUID = 1173984872572414699L;
    // 条件队列中第一个 node
    private transient Node firstWaiter;
    // 条件队列中最后一个 node
    private transient Node lastWaiter;
}  

ConditionObject 我们就称为条件队列,我们需要使用时,直接 new ConditionObject () 即可。


§ Node

static final class Node {
   
    /**
     * 同步队列单独的属性
     */
    static final Node SHARED = new Node(); // node 是共享模式
    static final Node EXCLUSIVE = null; // node 是排它模式
    // 当前节点的前节点
    // 节点 acquire 成功后就会变成head
    // head 节点不能被 cancelled
    volatile Node prev;

    // 当前节点的下一个节点
    volatile Node next;
    /**
     * 两个队列共享的属性
     */
    // 表示当前节点的状态,通过节点的状态来控制节点的行为
    // 普通同步节点,就是 0 ,条件节点是 CONDITION -2
    volatile int waitStatus;

    // waitStatus 的状态有以下几种
    // 被取消
    static final int CANCELLED =  1;

    // SIGNAL 状态的意义:同步队列中的节点在自旋获取锁的时候,如果前一个节点的状态是 SIGNAL,那么自己就可以阻塞休息了,否则自己一直自旋尝试获得锁
    static final int SIGNAL    = -1;

    // 表示当前 node 正在条件队列中,当有节点从同步队列转移到条件队列时,状态就会被更改成 CONDITION
    static final int CONDITION = -2;

    // 无条件传播,共享模式下,该状态的进程处于可运行状态
    static final int PROPAGATE = -3;

    // 当前节点的线程
    volatile Thread thread;

    // 在同步队列中,nextWaiter 并不真的是指向其下一个节点,我们用 next 表示同步队列的下一个节点,nextWaiter 只是表示当前 Node 是排它模式还是共享模式
    // 但在条件队列中,nextWaiter 就是表示下一个节点元素
    Node nextWaiter;
}
  • 从 Node 的结构中,我们需要重点关注 waitStatus 字段,Node 的很多操作都是围绕着 waitStatus 字段进行的。

  • Node 的 pre、next 属性是同步队列中的链表前后指向字段,nextWaiter 是条件队列中下一个节点的指向字段,但在同步队列中,nextWaiter 只是一个标识符,表示当前节点是共享还是排它模式。


3. AQS维护核心变量–state和waitStatus

  1. state 是锁的状态,是 int 类型,子类继承 AQS 时,都是要根据 state 字段来判断有无得到锁,比如当前同步器状态是 0,表示可以获得锁,当前同步器状态是 1,表示锁已经被其他线程持有,当前线程无法获得锁;
  2. waitStatus 是节点(Node,可以理解为线程)的状态,种类很多,一共有初始化 (0)、CANCELLED (1)、SIGNAL (-1)、CONDITION (-2)、PROPAGATE (-3),各个状态的含义可以见上文。

我们可以通过修改state字段实现多线程的独占/共享加锁模式:

image-20191217115324973


image-20191217115333553


4. AQS核心代码

  1. 首先调用可以由子类重写的tryAcquire方法获取资源(修改state)。
  2. 成功则无事,失败则入队addWaiter(Node.EXCLUSIVE)
  3. acquireQueued方法对入队节点进行相应操作使其获取资源/停止获取,中断。

一般来说,自定义同步器要么是独占方式,要么是共享方式,它们也只需实现tryAcquire-tryRelease、tryAcquireShared-tryReleaseShared中的一种即可。AQS也支持自定义同步器同时实现独占和共享两种方式,如ReentrantReadWriteLock。ReentrantLock是独占锁,所以实现了tryAcquire-tryRelease


1)acquire()以下整个过程以排他锁为例

acquire指定了获取锁的框架分为排他锁和共享锁。

// 排它模式下,尝试获得锁
public final void acquire(int arg) {
   
    // tryAcquire 方法是需要实现类去实现的,实现思路一般都是 cas 给 state 赋值来决定是否能获得锁
    if (!tryAcquire(arg) &&
        // addWaiter 入参代表是排他模式
        acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
        selfInterrupt();
}

img


a) tryAcquire()

​ 需要子类重写。

b)addWaiter()
private Node addWaiter(Node mode) {
   
    Node node = new Node(Thread.currentThread(), mode);
    Node pred = tail;
    // 1. 先尝试将node插入尾部
    if (pred != null) {
   // a
        node.prev = pred;
        
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值