一、AQS整体结构

AQS(AbstractQueuedSynchronizer)是 JDK 并发包 java.util.concurrent.locks 的核心同步框架。

它本身并不实现具体锁,而是提供了一套通用机制:

  • 状态管理(state)
  • 线程阻塞与唤醒
  • FIFO同步队列

很多并发组件都基于 AQS 实现:

  • ReentrantLock
  • ReentrantReadWriteLock
  • Semaphore
  • CountDownLatch

整体结构:

AQS的设计思想:

state负责资源状态

获取失败的线程进入队列

前驱节点释放资源后唤醒后继节点

FIFO保证公平性(公平锁)

二、核心成员变量

AQS最重要的成员变量只有三个:

private transient volatile Node head;

private transient volatile Node tail;

private volatile int state;

1、state同步状态

private volatile int state;

AQS所有同步器本质都围绕state展开

例如:

ReentrantLock

state = 0       表示无锁

state = 1       表示已经被占用

state = 5       表示重入5次

Semaphore

state = 3        表示还有3个许可证

CountDownLatch

state = 10   表示还有10个任务未完成

修改state全部依赖CAS:

compareAndSetState(expect, update)

底层:

Unsafe.compareAndSwapInt(...)

2、head  同步队列头节点

private transient volatile Node head;

特点:

head通常是哨兵节点

不对应真实线程

真正等待线程从head.next开始

例如:

head -> T1 -> T2 -> T3 

head为空节点。

3、tail  同步队列尾节点

private transient volatile Node tail;

所有新节点都从尾部插入形成双向链表:

head -> T1 -> T2 -> T3
                                 ↑
                                tail

三、AQS在JUC中的位置

AQS是锁吗?
为什么源码里总看到sync.acquire()?

实际上:

AQS 并不是锁

它只是 JDK 提供的一个同步器框架,用来解决以下通用问题:

  • 线程排队
  • 线程阻塞
  • 线程唤醒
  • 同步状态管理

真正对外提供锁功能的是 ReentrantLock

整体关系如下:

                Lock接口
                    ▲
                    │
             ReentrantLock
                    │
                    ▼
                  Sync
                    │
                    ▼
     AbstractQueuedSynchronizer

源码:

public class ReentrantLock implements Lock {

    private final Sync sync;

}

其中:

abstract static class Sync
        extends AbstractQueuedSynchronizer {
}

说明:

ReentrantLock
        ↓
      Sync
        ↓
      AQS

AQS负责同步框架

Sync负责具体同步规则

ReentrantLock负责向外提供 lock()/unlock() API

四、AQS的设计模式——模板方法模式

AQS最大的设计亮点就是模板方法模式

AQS规定了:

acquire()

release()

整个获取资源和释放资源的流程

tryAcquire()

tryRelease()

交给子类实现

例如:

public final void acquire(int arg) {

    if (!tryAcquire(arg) &&
        acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
        selfInterrupt();
}

tryAcquire(arg)

实际上会执行子类实现的方法

例如:

ReentrantLock.Sync.tryAcquire()

因此:

AQS负责:

    怎么排队
    怎么阻塞
    怎么唤醒

子类负责:

    什么情况下获取成功
    什么情况下释放成功

结构如下:

          AQS

        acquire()
        release()

             │

            ▼

      tryAcquire()
      tryRelease()

             │

            ▼

   ReentrantLock

      Semaphore

   CountDownLatch

这也是 AQS 能够支撑大量同步器实现的根本原因

五、AQS 是干什么的

AQS(AbstractQueuedSynchronizer)看着很复杂,实际上它只解决一个问题:

多个线程竞争资源时,如何管理等待线程?

例如:

ReentrantLock lock = new ReentrantLock();

lock.lock();

假设:

Thread-A 获取成功

Thread-B 获取失败

Thread-C 获取失败

会产生一下问题:

B怎么办?
C怎么办?
谁先获得锁?
怎么挂起?
怎么唤醒?

这些事情全部由 AQS 完成,所以可以先记住:

AQS = 锁的排队管理器

AQS只管理两样东西:

state
队列

结构:

          AQS
           │
    ┌──────┴──────┐
    │             │
 state          Queue

1、state表示资源状态

private volatile int state;

例如:

ReentrantLock

state = 0       表示无锁

state = 1       表示已经被占用

state = 5       表示重入5次

Semaphore

state = 3        表示还有3个许可证

CountDownLatch

state = 10   表示还有10个任务未完成

因此可以理解为state = 资源数量

2、Queue

获取资源失败的线程进入队列等待

例如:

Thread-A 获取锁

Thread-B 失败

Thread-C 失败

形成:

head -> B -> C

六、为什么需要 state

假设没有 state

线程来了:

A 获取锁

系统根本不知道锁是否被占用

所以需要一个变量记录

volatile int state;

例如

state=0表示没人占用

state = 0

state=1表示有人占用

state = 1

线程获取锁:

CAS(state,0,1)

成功则修改state=1代表拿到锁

失败则进入队列

所以:

state负责资源管理

七、为什么需要队列?

假设:A 持有锁

此时:B来、C来、D来  ——>   全部失败

如果没有队列:

B一直循环抢锁
C一直循环抢锁
D一直循环抢锁

CPU直接爆炸,所以:

抢不到

排队

睡觉

形成:

head -> B -> C -> D

这就是 AQS 队列存在的意义

八、AQS队列是什么?

AQS采用:

CLH变体双向队列

源码:

private transient volatile Node head;
private transient volatile Node tail;

队列结构:

head
 ↓
Node(B)
 ↓
Node(C)
 ↓
Node(D)

实际上是双向链表:

head <-> B <-> C <-> D

每个节点:

static final class Node {

    Node prev;

    Node next;

    Thread thread;
}

保存:

当前线程

前驱节点

后继节点

九、获取锁流程

例如ReentrantLock:

final void lock() {
    if (compareAndSetState(0, 1))
        setExclusiveOwnerThread(Thread.currentThread());
    else
        acquire(1);
}

进入:

acquire(1);

流程:

tryAcquire()

尝试抢锁

成功:结束

失败:创建Node

入队:

head -> B -> C

挂起——>线程睡觉:

LockSupport.park();

十、为什么不是立即抢锁

假设:

head -> B -> C -> D

锁释放后

如果B C D一起抢,导致CPU竞争非常激烈

AQS规定:

只有head.next
才能尝试获取锁

也就是说只有B能抢

源码:

if (p == head &&
    tryAcquire(arg))

这里p == head

就是我是队首

这样减少大量竞争

十一、释放锁流程

例如:

ReentrantLock.unlock();
public void unlock() {
        sync.release(1);
    }

进入release方法:

public final boolean release(int arg) {
        if (tryRelease(arg)) {
            Node h = head;
            if (h != null && h.waitStatus != 0)
                unparkSuccessor(h);
            return true;
        }
        return false;
    }

调用:tryRelease()方法修改state

例如  state = 0

然后  unparkSuccessor(head);

唤醒  head.next

例如:

head -> B -> C -> D

唤醒:B

B被唤醒后:

成功:成为新head

结果:head -> C -> D

更多推荐