java锁-AQS

java锁-AQS

详细讨论java中AQS底层加锁原理和应用。



前言

java中AQS是java.util.concurrent.locks.AbstractQueuedSynchronizer抽象类的缩写,大部分的锁都是基于这个类来实现的,如:ReentrantLock,ReentrantReadWriteLock和CountDownLatch等。这是一个基于队列实现的同步器,AbstractQueuedSynchronizer内部维护一张以双向链表的队列和一个全局持有锁状态state变量。本文详细讨论AQS的加锁和释放锁原理。

1. 原理

在这里插入图片描述

1.1 核心变量

内部维护了一个volatile int state(代表共享资源)和一个FIFO线程等待队列(多线程争用资源被阻塞时会进入此队列)。这里volatile是核心关键词,具体volatile的语义,在此不述。state的访问方式有三种:

  • getState()
  • setState()
  • compareAndSetState()

AQS定义两种资源类型:Exclusive独占,同一时刻只有一个线程能执行,如ReentrantLock)和Share共享,同一时刻能有多个共享线程同时执行,如Semaphore/CountDownLatch)。

1.2 核心方法

不同锁的实现,同步器争用共享资源的方式也不同。自定义同步器在实现时只需要实现共享资源state的获取与释放方式,至于具体线程等待队列的维护(如获取资源失败入队/唤醒出队等)由AQS实现。自定义同步器实现时主要实现以下几种方法:

  • isHeldExclusively()
    该线程是否正在独占资源。只有用到condition才需要去实现它。

  • tryAcquire(int)
    独占方式。尝试获取资源,成功则返回true,失败则返回false。

  • tryRelease(int)
    独占方式。尝试释放资源,成功则返回true,失败则返回false。

  • tryAcquireShared(int)
    共享方式。尝试获取资源。负数表示失败;0表示成功,但没有剩余可用资源;正数表示成功,且有剩余资源。

  • tryReleaseShared(int)
    共享方式。尝试释放资源,如果释放后允许唤醒后续等待结点返回true,否则返回false。

    上面5个方法都需要具体的实现类去实现获取锁和释放锁的逻辑,阻塞和唤醒由AQS来处理,这也是证明了它为什么是其他锁实现的基础类,想知道java中锁的实现原理,则理解AQS原理就 非常重要,非常重要,非常重要

1.3 加锁、释放锁流程

这里详细说明AbstractQueuedSynchronizer加锁和释放锁的实现逻辑。

1.3.1 独占锁加锁流程

在这里插入图片描述

1.3.2 独占锁释放流程

在这里插入图片描述

1.3.3 共享锁加锁流程

在这里插入图片描述

1.3.4 共享锁释放流程

在这里插入图片描述
看完这些流程图是不是有点头晕,别急,下面我们跟着java8中AbstractQueuedSynchronizer源码来分析,在代码中是如何实现。

2. 源码分析

这一节我们跟着源码来分析,博主使用java8中AbstractQueuedSynchronizer进行源码分析,不同版本源码可能存在一点差异。

2.1 独占锁加锁源码分析

独占锁的加锁入口方法是**acquire(1)**源码如下:

   /**
    * 独占锁的加锁入口方法
    * arg 加锁的次数
    */
    public final void acquire(int arg) {
   
        if (!tryAcquire(arg) &&
            acquireQueued(addWaiter(Node.EXCLUSIVE), arg)) 
            selfInterrupt();
    }
  • 步骤1:执行tryAcquire(arg)方法尝试获取锁,返回true说明获取锁成功直接结束,否则执行步骤2
  • 步骤2:执行addWaiter(Node.EXCLUSIVE)方法,创新的独占模式等待节点,加入到队列尾部,返回这个新节点node(新尾节点)。
  • 步骤3:执行acquireQueued(addWaiter(Node.EXCLUSIVE), arg)方法重新尝试获取锁,获取失败将当前线程挂起,返回当前线程的中断状态,返回ture时执行步骤4
  • 步骤4:执行selfInterrupt()方法将当前线程中断。

addWaiter(Node.EXCLUSIVE) 源码如下:

  /**
     * 用当前线程创建一个等待节点,并添加到队列的尾部
     *
     * @param mode Node.EXCLUSIVE 申明当前节点类型为独占模式
     * @return 返回当前创建的等待节点
     */
    private Node addWaiter(Node mode) {
   
        Node node = new Node(Thread.currentThread(), mode);
        // 申明一个变量pred 将队列的尾节点赋值给它(老尾节点)
        Node pred = tail;
        // 如果尾节点不为null,说明队列已被初始化
        if (pred != null) {
   
            // 将当前节点的前一个节点指针指向队列尾结点
            node.prev = pred;
            // 采用CAS方式将node节点修改为尾节点(新尾节点)
            if (compareAndSetTail(pred, node)) {
   
                // 修改成功后将老尾节点的下一个节点指针指向新尾节点
                pred.next = node;
                // 返回新尾节点
                return node;
            }
        }
         // 如果尾节点为null,说明队列未被初始化
        enq(node);
        // 返回新尾节点
        return node;
    }

    /**
     * 初始化并插入新节点
     * @param node 插入的新节点
     * @return 尾节点
     */
    private Node enq(final Node node) {
   
        // 采用自旋方式操作
        for (;;) {
   
            Node t = tail;
            // 如果尾节点为null,说明未初始化,必须先初始化
            if (t == null) {
    
                 // 将头节点通过CAS
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

罗德阿

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值