AQS
Java AQS(AbstractQueuedSynchronizer)是Java并发包(java.util.concurrent)中的一个基础同步框架。它为实现锁和其他同步器(如Semaphore、CountDownLatch、ReentrantLock等)提供了一个通用的底层机制。
核心特点:
- 基于FIFO队列:AQS通过一个基于FIFO的等待队列管理线程的同步状态。 独占与共享模式:支持独占(如ReentrantLock)和共享(如Semaphore)两种资源获取方式。
- 状态管理:通过一个int类型的state变量表示同步状态,子类通过继承AQS并重写相关方法实现自定义同步器。
- 阻塞与唤醒:AQS负责线程的阻塞与唤醒,简化了同步器的实现。 常见用法:
实现自定义锁或同步器时,继承AQS并实现tryAcquire、tryRelease等方法。 AQS极大地简化了Java并发工具的开发,是Java并发包的核心基础
数据结构
- Node
abstract static class Node {
volatile Node prev; // initially attached via casTail
volatile Node next; // visibly nonnull when signallable
Thread waiter; // visibly nonnull when enqueued
volatile int status; // written by owner, atomic bit ops by others
}
- CLH队列
CLH队列(Craig, Landin, and Hagersten队列)是一种基于链表的自旋锁队列,AQS的同步队列就是基于CLH队列实现的。
特点:
CLH队列是一个虚链表,节点代表等待获取锁的线程。 每个线程在本地保存一个前驱节点,通过前驱节点的状态判断自己是否可以获取锁。 线程入队时会自旋等待前驱节点释放锁,减少了竞争和CPU资源浪费。 在AQS中的应用:
AQS通过CLH队列实现线程的有序排队和唤醒。 线程获取锁失败时,会被包装成一个Node节点加入队列尾部。 只有队列头节点的下一个节点才有资格被唤醒尝试获取锁,保证了公平性。 CLH队列的设计让AQS能够高效地管理大量线程的同步等待。
头节点唤醒
在AQS中,等待队列的头节点(head)用于管理线程的同步和唤醒。其主要机制如下:
等待: 当线程获取锁失败时,会被封装成一个节点加入到AQS的同步队列(一个双向链表),并阻塞(通常通过LockSupport.park()实现)。只有队列头节点前面的线程被唤醒后,当前节点才有机会获取锁。
唤醒: 当锁被释放时,AQS会调用unparkSuccessor(head)方法唤醒队列中的下一个节点(即头节点的下一个节点)。具体做法是通过LockSupport.unpark()唤醒等待线程,使其重新尝试获取锁。
总结:
头节点本身不代表实际线程,只是一个占位节点。 唤醒操作总是针对头节点的下一个节点(即等待时间最长的线程)。 通过这种方式,实现了线程的有序等待和唤醒,保证了同步的公平性和效率。