Skip to main content

AQS

David LiuAbout 3 min

AQS

底层原理

AQS 核心思想

  • 如果被请求的共享资源空闲,则将当前请求资源的线程设置为有效的工作线程,并且将共享资源设置为锁定状态。
  • 如果被请求的共享资源被占用,那么就需要一套线程阻塞等待以及被唤醒时锁分配的机制,这个机制 AQS 是基于 CLH 锁 (Craig, Landin, and Hagersten locks) 实现的。

CLH 锁

CLH 锁是对自旋锁的一种改进,解决了自旋锁的两个缺点改进如下:

  • 饥饿问题:将线程组织成一个队列,保证先请求的线程先获得锁,避免了饥饿问题。
  • 锁状态去中心化,让每个线程在不同的状态变量中自旋,相当于减小锁的粒度,这样当一个线程释放它的锁时,只能使其后续线程的高速缓存失效,缩小了影响范围,从而减少了 CPU 的开销。

是一个虚拟的双向队列(虚拟的双向队列即不存在队列实例,仅存在结点之间的关联关系),暂时获取不到锁的线程将被加入到该队列中。AQS 将每条请求共享资源的线程封装成一个 CLH 队列锁的一个结点(Node)来实现锁的分配。

在 CLH 队列锁中,一个节点表示一个线程,它保存着线程的引用(thread)、 当前节点在队列中的状态(waitStatus)、前驱节点(prev)、后继节点(next)。

CLH 锁作为自旋锁的改进,有以下几个优点:

  1. 性能优异,获取和释放锁开销小。CLH 的锁状态不再是单一的原子变量,而是分散在每个节点的状态中,降低了自旋锁在竞争激烈时频繁同步的开销。在释放锁的开销也因为不需要使用 CAS 指令而降低了。
  2. 公平锁。先入队的线程会先得到锁。
  3. 实现简单,易于理解。
  4. 扩展性强。下面会提到 AQS 如何扩展 CLH 锁实现了 juc 包下各类丰富的同步器。

当然,它也有两个缺点:

  1. 第一是因为有自旋操作,当锁持有时间长时会带来较大的 CPU 开销。
  2. 第二是基本的 CLH 锁功能单一,不改造不能支持复杂的功能。

AQS 改进

  • 阻塞队列代替自旋操作

AQS 使用了模板方法模式

子类要重写如下五个钩子

//独占方式。尝试获取资源,成功则返回true,失败则返回false。
protected boolean tryAcquire(int)
//独占方式。尝试释放资源,成功则返回true,失败则返回false。
protected boolean tryRelease(int)
//共享方式。尝试获取资源。负数表示失败;0表示成功,但没有剩余可用资源;正数表示成功,且有剩余资源。
protected int tryAcquireShared(int)
//共享方式。尝试释放资源,成功则返回true,失败则返回false。
protected boolean tryReleaseShared(int)
//该线程是否正在独占资源。只有用到condition才需要去实现它。
protected boolean isHeldExclusively()