文章目录
- AQS
- 一、简介
- 二、优势
- 2.1 屏蔽细节
- 2.2 提高性能
- 2.3 AQS体系
- 三、AQS源码初探
- 3.1 同步状态
- 3.2 同步队列
- 3.3 主要方法
- 3.3.1 同步状态的操作方法
- 3.3.2 子类重写方法
- 3.3.2.1 独占式
- 3.3.2.2 共享式
- 3.3.2.3 源码
- 3.3.3 模板方法
- 3.3.3.1 独占式
- 3.3.3.2 共享式
- 3.3.3.3 源码
- 四、小结
- 4.1 方法结构
- 4.2 AQS小结
AQS
一、简介
- AQS,AbstractQueuedSynchronizer ,即队列同步器。它是构建锁或者其他同步组件的基础框架(如ReentrantLock、ReentrantReadWriteLock、Semaphore 等),J.U.C 并发包的作者(DougLea)期望它能够成为实现大部分同步需求的基础。它是JUC并发包中的核心基础组件。
二、优势
2.1 屏蔽细节
- AQS 解决了在实现同步器时涉及当的大量细节问题,例如获取同步状态、FIFO同步队列。基于AQS来构建同步器可以带来很多好处。它不仅能够极大地减少实现工作,而且也不必处理在多个位置上发生的竞争问题。
2.2 提高性能
- 在基于AQS 构建的同步器中,只能在一个时刻发生阻塞,从而降低上下文切换的开销,提高了吞吐量。同时在设计AQS时充分考虑了可伸缩性,因此JUC中,所有基于AQS构建的同步器均可以获得这个优势。在AQS中使用了模板模式,通过基础AQS实现特定的方法,可以方便的实现自定义的同步器,如锁等。
2.3 AQS体系
- AQS在JUC中的地位
三、AQS源码初探
3.1 同步状态
- AQS使用一个int类型的成员变量state来表示同步状态,在处理线程同步的过程中,各个线程本质是就是对这个state来进行读写操作,根据操作的结果决定线程是执行还是进入排队队列,具体细节暂不展开,先了解state。
当 state > 0 时,表示已经获取了锁。(对于可重入锁state表示重入次数)
当 state = 0 时,表示释放了锁。
3.2 同步队列
- AQS通过内置的FIFO同步队列来完成资源获取线程的排队工作。如果当前线程获取同步状态失败(锁)时,AQS则会将当前线程以及等待状态等信息构造成一个节点(Node)并将其加入同步队列,同时会阻塞当前线程。当同步状态释放时,则会把节点中的线程唤醒,使其再次尝试获取同步状态。
3.3 主要方法
- AQS 主要提供了如下方法:
3.3.1 同步状态的操作方法
- getState():返回同步状态的当前值。
- setState(int newState):设置当前同步状态。
- compareAndSetState(int expect, int update):使用 CAS设置当前状态,该方法能够保证状态设置的原子性。
3.3.2 子类重写方法
3.3.2.1 独占式
- 【可重写】tryAcquire(int arg):独占式获取同步状态,获取同步状态成功后,其他线程需要等待该线程释放同步状态才能获取同步状态。
- 【可重写】tryRelease(int arg):独占式释放同步状态。
3.3.2.2 共享式
- 【可重写】tryAcquireShared(int arg):共享式获取同步状态,返回值大于等于0,则表示获取成功;否则,获取失败。
- 【可重写】tryReleaseShared(int arg):共享式释放同步状态。
- 【可重写】isHeldExclusively():当前同步器是否在独占式模式下被线程占用,一般该方法表示是否被当前线程所独占。
3.3.2.3 源码
- 可重写的方法,在AQS中都没有实现,都是抛出一个异常,交给子类实现,如下:
protected boolean tryAcquire(int arg) {
throw new UnsupportedOperationException();
}
3.3.3 模板方法
3.3.3.1 独占式
- acquire(int arg):独占式获取同步状态。如果当前线程获取同步状态成功,则由该方法返回;否则,将会进入同步队列等待。该方法将会调用可重写的 #tryAcquire(int arg) 方法;
- acquireInterruptibly(int arg):与acquire(int arg) 相同,但是该方法可以响应中断。当前线程为获取到同步状态而进入到同步队列中,如果当前线程被中断,
则该方法会抛出InterruptedException 异常并返回。 - tryAcquireNanos(int arg, long nanos):超时获取同步状态。如果当前线程在nanos时间内没有获取到同步状态,那么将会返回false,已经获取则返回true 。
- release(int arg):独占式释放同步状态。该方法会在释放同步状态之后,将同步队列中第一个节点包含的线程唤醒。
3.3.3.2 共享式
- acquireShared(int arg):共享式获取同步状态。如果当前线程未获取到同步状态,将会进入同步队列等待,与独占式的主要区别是在同一时刻可以有多个线程获取到同步状态;
- acquireSharedInterruptibly(int arg):共享式获取同步状态,可以响应中断。
- tryAcquireSharedNanos(int arg, long nanosTimeout):共享式获取同步状态,增加超时限制。
- releaseShared(int arg):共享式释放同步状态。
3.3.3.3 源码
- 在模板方法中都会调用子类重写方法,因为模板方法是将已经固定的方法调用流程固定下来,只将逻辑实现不同的部分交给子类重写。如下是acquire(int arg)方法的源码,
我们可以看到,他是基于tryAcquire(arg)来实现的,而tryAcquire(arg)就是交给子类重写的方法,在acquireQueued里面我们也可以看到tryAcquire(arg)的身影。因为整个逻
辑流程可以在AQS中固定下来,只有tryAcquire(arg)的部分可能因为子类的不同而实现不同,这是典型的模板模式的应用。
public final void acquire(int arg) {
if (!tryAcquire(arg) &&
acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
selfInterrupt();
}
final boolean acquireQueued(final Node node, int arg) {
boolean failed = true;
try {
boolean interrupted = false;
for (;;) {
final Node p = node.predecessor();
if (p == head && tryAcquire(arg)) {
setHead(node);
p.next = null; // help GC
failed = false;
return interrupted;
}
if (shouldParkAfterFailedAcquire(p, node) &&
parkAndCheckInterrupt())
interrupted = true;
}
} finally {
if (failed)
cancelAcquire(node);
}
}
四、小结
4.1 方法结构
- 从上面的方法看下来,基本上可以分成 3 类:
1. 交给子类重写的方法。在3.4.1和3.4.2中的方法,这部分方法主要是给子类进行重写,来实现不同的锁功能,
在不同的锁或者同步工具类中,这几个方法的实现逻辑可能会不一样,因此AQS将该部分方法交给子类重写。
2. 模板方法:模板方法是AQS中实现逻辑的核心方法,在模板方法中会调用交给子类重写的方法来完成指定的逻
辑,这部分方法的逻辑已经固定的,只是把流程不一样的地方抽象出来给子类重写。
3. 查询同步队列中的等待线程情况,这部分方法重写不多
- 交给子类重写的方法和模板方法都分为独占式和共享式,方法基本是是对称分布的,通过方法名称很好分别。
- 子类重写方法所示如下:
方法作用 | 独占式 | 共享式 |
获取同步状态 | tryAcquire(int arg) | tryAcquireShared(int arg) |
释放同步状态 | tryRelease(int arg) | tryReleaseShared(int arg) |
判断是否被当前线程所独占 | isHeldExclusively() |
- 模板方法所示如下:
方法作用 | 独占式 | 共享式 |
获取同步状态 | acquire(int arg) | acquireShared(int arg) |
获取同步状态(可响应中断) | acquireInterruptibly(int arg) | acquireSharedInterruptibly(int arg) |
获取同步状态(可超时) | tryAcquireNanos(int arg, long nanos) | tryAcquireSharedNanos(int arg, long nanosTimeout) |
释放同步状态 | release(int arg) | releaseShared(int arg) |
4.2 AQS小结
- 本文我们主要是对AQS做一个初步的认识,AQS在整个并发体系中非常重要,它是很多显示锁和同步工具的实现基础。
- 在AQS中定义了通用的线程之间的同步等待逻辑,使用了模板模式来让子类的实现变得尽量简单,其内部使用FIFO的队列来完成线程之间的同步等待工
作,子类继承AQS来实现不同场景下的功能。 - 关于AQS更加详细的分析在后续的文章展开。