你的位置:首页 > 信息动态 > 新闻中心
信息动态
联系我们

AQS的源码学习

2021/11/21 20:34:31

AQS的源码学习

  • AQS是什么
  • 几个重要概念
    • 同步队列
    • Node节点
    • 状态变量state
    • 条件变量等待队列
  • 源码分析ReentrantLock加锁解锁过程
    • 加锁
    • 解锁
    • 图解

AQS是什么

AbstractQueuedSynchronizer(AQS),抽象队列同步器,并发编程的核心,框架。AQS相当于一个模板,具体实现由子类完成,例如ReentrantLock、CountDownLatch、Semaphore都是基于AQS实现的。重入锁和信号量都在自己内部,实现了一个AbstractQueuedSynchronizer的子类Sync。

几个重要概念

AQS内部维护了以下几个变量

同步队列

一个先进先出的队列,它是一个双向链表,用来存放lock()操作后进入等待的线程。

Node节点

同步队列存储的元素称为Node,该节点标识了节点状态、独占还是共享模式以及前驱后继节点等信息。

节点状态(waitStatus)分为:

  • CANCELLED(取消)=1:表示线程取消了等待。如果取得锁的过程中发生了一些异常,则可能出现取消的情况,比如等待过程中出现了中断异常或者出现了timeout。
  • SIGNAL(信号)= -1:表示后续节点需要被唤醒。
  • CONDITION(状态)= -2:线程等待在条件变量队列中。
  • PROPAGATE(传播)= -3:引入这个状态是为了解决共享锁并发释放引起线程挂起的bug。
  • 0: 初始状态

解锁时,需要判断节点状态是否<0,小于等于才需要被唤醒

状态变量state

state是否等于0,等于0则说明没有线程持有锁,可重入情况下会大于1。

条件变量等待队列

为了维护等待在条件变量上的等待线程,AbstractQueuedSynchronizer 又需要再维护一个条件变量等待队列,也就是那些由 Condition.await() 引起阻塞的线程,等待队列可以有多个。

当 condition 条件满足时,线程会从条件变量等待队列重新进入同步队列进行获取锁的竞争。

源码分析ReentrantLock加锁解锁过程

ReentrantLock lock = new ReentrantLock();
ReentrantLock的构造器可以传入一个boolean参数,true表示公平锁,false表示非公平锁,默认为false
在这里插入图片描述

加锁

首先上锁:lock.lock();

同以下代码可以看到,该方法调用了Sync类下的lock()方法。
在这里插入图片描述
进入lock()后,发现Sync类是ReentrantLock中的一个抽象静态内部类,没有实现lock()方法。
在这里插入图片描述
因为以非公平锁为例,于是转到NonfairSync(继承Sync,也是在ReentrantLock类中)中继续探究lock()方法的具体实现,通过源码可以知道,lock()方法通过一个cas操作尝试获取锁。

  • 若cas成功则执行同步代码。
  • 若cas失败,执行acquire()方法。
    在这里插入图片描述

接下来,我们看一下这个acquire()方法,点进去之后发现是AQS中的一个方法
在这里插入图片描述
此方法包含了三个重要方法

  • tryAcquire()
  • addWaiter()
  • acquireQueued()

先来研究下tryAcquire()方法,进入之后发现没有实现,那应该是子类中有具体实现,我们跳到子类ReentrantLock中查看这个方法。
在这里插入图片描述
果然,内部调用了nonfairTryAcquire()方法,我们进入此方法
在这里插入图片描述
nonfairTryAcquire():获取状态值state,state=0 意味着没有线程持有锁(可重入情况下会>1),又执行了一个cas操作
在这里插入图片描述
于是,我们知道了tryAcquire()方法就是内部通过cas操作尝试获取锁,我们继续看下一个方法 addWaiter()。
此方法将当前线程当做一个节点node,并试图加入到同步队列中。
PS:这里通过cas操作优化性能,如果失败再通过 enq(node); 将线程添加到队尾。
在这里插入图片描述
最后就是 acquireQueued() 方法,它是存储在队列中的线程尝试获取锁的方法,内部也是调用了一个tryAcquire()方法,此方法之前也是提到过,通过cas获取锁。
这里 if 有两个判断条件:1.p 要是头节点(头节点意味着正在执行同步代码)2.获取锁成功
(这里对p是头节点有点不理解,线程插入到队尾,它的前驱不是头节点,就不尝试获取锁了吗?非公平锁不是无论何时进入队列都会尝试获取锁吗?)
两个条件都满足才执行代码,否则执行 parkAndCheckInterrupt();,它内部调用了 LockSupport.park(),表示挂起当前线程。
PS:这是第三次尝试获取锁。
在这里插入图片描述
三个方法大致了解后,回到最初的方法 acquire();,这里需要了解 逻辑与&& 的特性,若&&之前的条件不成立,那么 && 之后的条件就不会执行了(短路)。我们发现只有当获取锁失败的情况(未获取锁返回false)下才会执行后面的语句,通过上面的分析,我们知道,当cas获取失败,将线程以node的形式加入队列,并在队列中再次尝试获取锁,如果获取失败,就此挂起。
PS:终于结束了:)
在这里插入图片描述

解锁

过两天补充

图解

在这里插入图片描述

个人理解,如有不对,欢迎批评指正^^