了解AQS,你就了解锁的原理

102 阅读2分钟

image.png

一、定义

  • 抽象的队列同步器

  • 锁和其他同步器组件的基石

    • ReentrantLock、CountDownLatch、Semaphore

      • 内含静态内部类sync继承AQS
      • 加锁和解锁底层都是通过AQS实现

二、基本结构

  • 属性

    • volatile int state(代表共享资源):0=空闲,1=锁占有
  • FIFO的双向队列

    • CLH队列

      • 单向链表
    • AQS队列是CLH变体的head,tail节点的虚拟双向链表

      • head和tail都是虚节点
    • 线程封装成Node节点

      • Node里面包含内部变量volatile int waitStatus+前后节点对象

      • waitStatus表示节点在队列中的状态

        • CANCELLED=1

          • 表示线程取消了等待
        • SIGNAL=-1

          • 表示后续节点需要被唤醒
        • 通过waitStatus小于等于0,来判断是否是CANCELLED状态

三、加锁

  • new ReentrantLock().lock()

  • 非公平锁 NonfairSync#lock()

image.png

  • compareAndSetState(0, 1) 通过 CAS 的方式尝试将 state 从0改为1

    • true,获取锁成功,将当前线程设置为独占线程,结束
  • 获取锁失败调用acquire(1)

image.png

  • tryAcquire 再次尝试获取锁资源,如果尝试成功,返回true,尝试失败返回false

image.png 获取state,判断如果等于0,通过cas尝试获取锁;否则判断线程如果是当前占有锁线程,state+1可重入,都不是返回fasle获取锁失败

  • 尝试获取锁资源失败,将当前线程封装成一个Node,追加到AQS的队列中

    • addWaiter() 将当前线程封装成Node节点加到队列尾部
  • acquireQueued()将已经在队列中的node尝试去获取锁否则挂起。

image.png

获取当前线程节点的上一节点,如果为头节点(头节点没有意义),说明当前节点是head后的第一个节点,再次尝试获取锁,成功将当前节点设置为头节点

  • 如果p不是head节点或者没有拿到锁资源执行shouldParkAfterFailedAcquire()

image.png

获取上一节点的等待状态,如果为-1直接返回true

image.png

image.png

执行parkAndCheckInterrupt()方法,通过LockSupport.park()方法阻塞当前线程。等以后执行unpark方法的时候,会继续acquireQueued()死循环获取锁逻辑,直到获取锁资源并结束死循环。

大于0说明上一节点已失效,剔除,一直向前查找有效节点,直到找到第一个ws<=0的节点为止,将node节点挂到该节点后面

小于0,通过cas将等待状态设置为-1

返回false,继续死循环获取锁

四、解锁

  • new ReentrantLock().unlock()

image.png

  • tryRelease尝试释放锁资源,如果释放成功,把AQS队列的节点用unpack()唤醒

image.png

tryRelease作用将state置为0

  • unparkSuccessor()用于唤醒AQS中被挂起的线程。

image.png