前言
上一篇文章 ReentrantLock 源码分析 - 基础篇 对 AbstractQueuedSynchronizer
(AQS) 的相关 概念 和 作用 作了简要介绍, 本文将继续对 AQS 做进一步介绍 (基于 jdk1.8 源码)。
阅读之前需要了解什么是 共享锁 和 独占锁 .
正文
上一篇有提到 CountDownlatch, ReentrantLock, ReentrantReadWriteLock
等并发包工具类都是基于 AQS 实现的, 这要求 AQS 需要提供很 丰富 并且 通用 的功能,
比如:
ReentranLock
是独占锁, 而 ReentrantReadWriteLock
是共享锁, 他们分别调用 AQS 的 acquire
方法去获取独占资源, acquireShared
方法去获取共享资源。但是又共同调用了 compareAndSetState
方法去实现 CAS 操作。
这还只是这两个类的之间的区别, 而 java.util.concurrent
包下面有诸多工具类,
因此, 我们暂时没必要全面了解 AQS 的所有功能
那么, 接下来就着重分析 AQS 在 ReentrantLock
的作用。
先来看一张 AQS 的类图
从图中可以看出 :
- AQS 继承自
AbstractOwnableSynchronizer
- AQS 有两个内部类
ConditionObject
和Node
对于研究 ReentrantLock
, 我们暂时只需要关注 Node 类, 和 AQS 中的部分 属性 和 方法
1. Node 类
上图为 Node
的所有属性
prev
和next
分别为前、后指针thread
存放的是线程对象SHARED
标记当前线程是因为抢夺共享资源,而被放入 AQS 队列的EXCLUSIVE
标记当前线程是因为抢夺独占资源, 而被放入 AQS 队列的
上一篇文章 ReentrantLock 源码分析 - 基础篇 有提到, AQS 是一个双向队列, 队列中的元素就是 Node
对象
2. AQS 的几个属性
// 指向队列的头
private transient volatile Node head;
// 指向队列的尾
private transient volatile Node tail;
// 上一篇也有讲到在 ReentrantLock 中的作用,表示重入次数
private volatile int state;
3. AQS 的几个方法
acquire(int arg)
release(int arg)
compareAndSetState(long expect, long update)
3.1 acquire(int arg)
获取独占资源
该方法大致的逻辑是:
尝试去获取锁, 如果获取锁失败,就把当前线程放入队列, 并把当前线程挂起
public final void acquire(int arg) {
// tryAcquire 尝试去获取锁, 而 !tryAcquire 则表示获取锁失败
// Node.EXCLUSIVE 上面有讲到是标记当前线程是获取独占资源失败
// addWaiter 放入 AQS 队列
// acquireQueued 这里可以先简单理解为 阻塞住了
if (!tryAcquire(arg) && acquireQueued(addWaiter(Node.EXCLUSIVE), arg)) {
selfInterrupt();
}
}
其中 tryAcquire, addWaiter, acquireQueued
三个方法内部具体逻辑, 下一篇文章会继续深入讲解。
3.2 release(long arg)
释放独占资源
该方法大致的逻辑是:
当一个线程调用 release
方法时, 会调用 tryRelease
尝试释放资源(通过设置 state
属性), 然后通过unparkSuccessor
最终调用 LockSupport.unpark
方法去激活 AQS 队列中的被阻塞的线程。
public final boolean release(long arg) {
if (tryRelease(arg)) {
Node h = head;
if (h != null && h.waitStatus != 0)
unparkSuccessor(h);
return true;
}
return false;
}
3.2.1 tryAcquire 和 tryRelease 方法
上一篇有提到 AQS 是一个抽象类, tryAcquire 和 tryRelease
两个方法在这个抽象类中并没有具体实现。它们是交给具体的子类来实现的,。
在ReentrantLock
中, 当 state
为 0 时,表示锁未被占用, 为 1 时,表示被占用;
而其内部的tryAcquire
方法, 会使用 CAS 操作判断当前的 state
是否为 0,
- 如果是, 就通过 CAS操作,将
state
设为 1, 并设置当前锁的拥有者为当前线程, 然后返回 true - 如果 CAS 操作失败,就返回 false.
相反的, ReentrantLock
内部在实现tryRelease
方法时, 会使用 CAS 操作把 state
的值从 1 修改为 0, 并设置当前锁的拥有者为 null, 然后返回 true, 如果 CAS 操作失败,就返回 false.
3.3 compareAndSetState(long expect, long update)
该方法直接调用了 unsafe
对象的 native 方法, 提供了 CAS 操作, 这一点在上一篇文章 ReentrantLock 源码分析 - 基础篇 中也有提到。
protected final boolean compareAndSetState(long expect, long update) {
// See below for intrinsics setup to support this
return unsafe.compareAndSwapLong(this, stateOffset, expect, update);
}
总结
ReentrantLock, CountDownlath
等并发工具类有其各自的作用, 而 AQS 是实现这些不同作用的基础,。
AQS 本质上是一个 双向队列, 当多个线程通过 ReentrantLock 对象的 lock()方法
同时去 尝试抢占锁 时, 未成功获取锁的线程会被放入 AQS 队列 中去排队等待,
而尝试去抢占锁的方法 tryAcquire
是一个抽象方法, 由继承 AQS 的子类去自行实现, tryAcquire
内部通常会通过 CAS 操作去判断获取锁的结果。
来源:oschina
链接:https://my.oschina.net/u/3984985/blog/4235670