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