一文带你快速掌握AQS

本文详细介绍了Java并发库中的AbstractQueuedSynchronizer(AQS),它是实现阻塞锁和其他同步器的基础。AQS通过维护一个同步队列和条件队列来管理线程的阻塞和唤醒。文章讨论了AQS的数据结构、同步队列(独占模式和共享模式)以及条件队列的工作原理,同时强调了AQS在构建锁和同步器中的核心作用。
摘要由CSDN通过智能技术生成
image

AbstractQueuedSynchronizer简介

AbstractQueuedSynchronizer抽象队列同步器,简称为AQS,可用于构建阻塞锁或者其他相关同步器的基础框,是Java并发包的基础工具类。通过AQS这个框架可以对同步状态原子性管理、线程的阻塞和解除阻塞、队列的管理进行统一管理。
AQS是抽象类,并不能直接实例化,当需要使用AQS的时候需要继承AQS抽象类并且重写指定的方法,这些重写方法包括线程获取资源和释放资源的方式(如ReentractLock通过分别重写线程获取和释放资源的方式实现了公平锁和非公平锁),同时子类还需要负责共享变量state的维护,如当state为0时表示该锁没有被占,大于0时候代表该锁被一个或多个线程占领(重入锁),而队列的维护(获取资源失败入队、线程唤醒、线程的状态等)不需要我们考虑,AQS已经帮我们实现好了。AQS的这种设计模式采用的正是模板方法模式
总结起来子类的任务有:

  1. 通过CAS操作维护共享变量state
  2. 重写资源的获取方式。
  3. 重写资源释放的方式。

如果对CAS和Java内存模型还不清楚的,建议先了解这两者之后再食用本文,效果更佳!CAS原理分析及ABA问题详解 什么是Java内存模型?

完成以上三个任务即可实现自己的锁。
AQS作为J.U.C的工具类,面向的是需要实现锁的实现者,而锁面向的是锁的使用者,这两者的区别还是需要搞清楚的。

AQS数据结构

先看AQS有哪些重要的成员变量。

// 头结点,你直接把它当做 当前持有锁的线程 可能是最好理解的
private transient volatile Node head;

// 阻塞的尾节点,每个新的节点进来,都插入到最后,也就形成了一个链表
private transient volatile Node tail;

// 这个是最重要的,不过也是最简单的,代表当前锁的状态,0代表没有被占用,大于0代表有线程持有当前锁
// 之所以说大于0,而不是等于1,是因为锁可以重入嘛,每次重入都加上1
private volatile int state;

// 代表当前持有独占锁的线程,举个最重要的使用例子,因为锁可以重入
// reentrantLock.lock()可以嵌套调用多次,所以每次用这个来判断当前线程是否已经拥有了锁
// if (currentThread == getExclusiveOwnerThread()) {state++}
private transient Thread exclusiveOwnerThread; //继承自AbstractOwnableSynchronizer

然后再看看AQS的内部结构,AQS内部数据结构为一个双向链表和一个单向链表,双链表为同步队列,队列中的每个节点对应一个Node内部类,AQS通过控制链表的节点而达到阻塞、同步的目的,单链表为条件队列,可以把同步队列和条件队列理解成储存等待状态的线程的队列,但是条件队列中的线程并不能直接去获取资源,而要先从条件队列转到同步队列中排队获取,同步队列的唤醒结果是线程去尝试获取锁,而条件队列的唤醒结果是把线程从条件队列移到同步队列中,一个线程要么是在同步队列中,要么是在条件队列中,不可能同时存在这两个队列里面。

image

Java阻塞状态等待状态的线程从Linux内核来看,都是阻塞(等待)状态,它们都会让出CPU时间片。Java为了方便管理线程将“阻塞(等待)”状态细分成了阻塞状态和等待状态,这两个状态的区别在于由谁去唤醒,是操作系统还是其他线程。Java线程请求某一个资源失败的时候就会进入阻塞状态,处于阻塞态的线程会不断请求资源,一旦请求成功,就会进入就绪队列,等待执行。而当线程调用waitjoinpack函数时候会进入等待状态,需要其它线程显性的唤醒否则会无限期的处于等待状态。

Java线程6状态图:
image

内部类Node详解:

static final class Node {
    //代表当前节(线程)点是共享模式
    static final Node SHARED = new Node();
    //代表当前节点(线程)是独占模式
    static final Node EXCLUSIVE = null;
    //代表当前节点(线程)已被取消
    static final int CANCELLED =  1;
    //代表当前节点(线程)的后继节点需要被提醒唤醒
    static final int SIGNAL    = -1;
    //代表节点(线程)在 Condition queue中,等待某一条件
    static final int CONDITION = -2;
    //代表当前节点的后继节点(线程)会传传播唤醒的操作,仅在共享模式下才有作用
    static final int PROPAGATE = -3;
    //代表当前节点的状态,它的取值除了以上说的CANCELLED、SIGNAL、CONDITION、PROPAGATE,同时
    //还可能为0,为0的时候代表当前节点在sync队列中,阻塞着排队获取锁。
    volatile int waitStatus;
    //当前节点的前驱节点
    volatile Node prev;
    //当前节点的后继节点
    volatile Node next;
    //当前节点关联的线程
    volatile Thread thread;
    //在condition队列中的后继节点
    Node nextWaiter;
    
    //判断当前节点是否为共享模式
    final boolean isShared() {
        return nextWaiter == SHARED;
    }
    
    //返回当前节点的前驱节点 没有前驱节点则抛出异常
    final Node predecessor() throws NullPointerException {
        Node p = prev;
        if (p == null)
            throw new NullPointerException();
        else
            return p;
    }    
}

每个线程都关联一个节点,节点的状态也代表着线程的状态,AQS通过对同步队列的管理而达到对线程的管理。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值