什么是AQS
AQS(AbstractQueuedSynchronizer)是Java中一个抽象的同步框架,它提供了一些基本的同步工具(如独占锁、共享锁等)的实现,并且能够让开发者通过继承它来实现自定义的同步工具。AQS内部维护了一个FIFO的线程等待队列,通过CAS操作等机制和内置的休眠和唤醒的机制实现了线程的挂起和恢复。
AQS的核心思想是将“状态”和“线程”分离开来,将“状态”保存在AQS内部的某个属性变量中,而将“线程”保存在AQS内部的一个FIFO的等待队列中。当需要获取锁时,线程会尝试CAS操作去修改AQS内部保存的状态,如果CAS操作成功,则表示该线程成功获得锁;否则,线程就会被放入等待队列,并进入阻塞状态,直到被唤醒或超时。
AQS支持两种同步方式:独占模式(Exclusive Mode)和共享模式(Shared Mode)。独占模式下,同一时刻只有一个线程可以获取到锁,例如ReentrantLock
就是以独占模式实现的。共享模式下,多个线程可以同时获取到锁,例如读写锁等就是以共享模式实现的。
AQS被广泛地应用在Java并发工具类的实现中,如Semaphore、CountDownLatch、ReentrantLock等。开发者可以通过继承AQS来实现自定义的同步工具,在高并发场景中提高程序的性能和可靠性。
AQS具备的特性
-
高度可扩展性:AQS提供了基于状态(state)和双向队列(queue)的底层机制,使得它对各种锁和同步器具有极高的可扩展性。
-
独占和共享:AQS支持独占模式和共享模式的同步器。独占模式下只允许一个线程持有锁,共享模式下允许多个线程同时获得访问权。
-
可重入性:对于独占式同步器,AQS通过记录当前持有锁的线程和它持有的次数来实现可重入性。
-
条件变量:AQS提供了基于条件变量的机制,可以让线程在某个条件满足时等待或唤醒。
-
阻塞式同步器:AQS是通过阻塞线程来实现同步机制的,在队列上等待的线程不会消耗CPU资源,从而减少了竞争,提高了性能。
-
公平性:AQS支持公平和非公平两种模式,公平模式下按照线程申请时间的先后顺序获取锁,非公平模式下直接抢占锁,哪个线程抢到了就由哪个线程执行。
volatile int state
表示同步状态的核心属性
1. 作用和含义
-
同步状态的表示:
-
state
属性通常用来表示某种共享资源的状态或者数量。具体的含义和用途取决于具体的同步器实现。例如,在 ReentrantLock 中,state
表示锁的重入次数;在 Semaphore 中,state
表示可用的许可数量。
-
2. volatile 关键字的作用
-
保证可见性:
-
声明为
volatile int state
,保证了多个线程对state
进行读取操作时能够看到最新的值。任何一个线程修改了state
的值,对其他线程是立即可见的,避免了线程之间的数据不一致问题。
-
3. 并发控制基础
-
CAS 操作支持:
-
AQS 内部的同步操作通常使用 CAS(Compare And Swap)指令来实现。
state
的volatile
特性对 CAS 操作是必要的,因为 CAS 操作本身依赖于读取和写入的原子性保证,而volatile
可确保在读取时获取的是最新的值。
-
4. 具体应用举例
-
ReentrantLock:
-
在 ReentrantLock 中,
state
表示当前锁的状态。具体来说,当state
为 0 时表示锁是空闲状态;大于 0 表示锁被某个线程重入的次数。每次线程获取锁时,会对state
进行更新和维护。
-
-
Semaphore:
-
在 Semaphore 中,
state
表示当前可用的许可数量。每当一个线程请求许可时,会尝试减少state
的值,直到state
为 0 时,其他线程需要等待许可释放。
-
5. 使用场景和注意事项
-
高效的并发控制:
-
state
的设计使得 AQS 能够支持高效的并发控制,无论是独占锁还是共享锁,通过合理地使用state
变量,可以实现复杂的同步模式。
-
-
注意原子性操作:
-
尽管
volatile
保证了可见性,但并不保证原子性。因此,AQS 在进行更新state
的操作时,通常需要使用 CAS 操作来确保线程安全性。
-
AQS定义两种资源共享的方式
1. 独占(Exclusive)
独占模式下,资源只能被一个线程占用。在 AQS 中,独占模式通过维护一个状态变量(通常是 state
)来管理。当一个线程获取到独占资源时,其他线程必须等待该线程释放资源才能继续尝试获取。
在 AQS 中,实现独占模式的核心方法是:
-
tryAcquire(int arg)
:尝试获取资源。通常通过 CAS 操作修改state
的值来表示资源被占用的状态。如果获取成功,返回true
,否则返回false
。 -
tryRelease(int arg)
:尝试释放资源。释放资源会涉及修改state
的值,以便其他等待线程可以继续尝试获取资源。
独占模式适用于那些一次只能被一个线程访问的临界资源,如互斥锁的实现就是一个典型的独占资源的应用场景。
2. 共享(Shared)
共享模式下,资源可以同时被多个线程访问。这种模式下,AQS 允许多个线程同时获取资源,而不像独占模式那样只允许一个线程持有资源。
在 AQS 中,共享模式通过维护一个状态变量来表示当前共享资源的数量或者其他共享的状态信息。常见的共享模式有:
-
共享锁(Shared Lock):也称为读锁。多个线程可以同时获取到共享锁,以读取共享资源,但是不允许写入。只有当所有线程释放共享锁后,才能进行写操作。
-
信号量(Semaphore):控制同时访问特定资源的线程数目。
在 AQS 中,实现共享模式的方法与独占模式类似,但通常需要更复杂的逻辑来管理多个线程对资源的访问。共享模式适用于那些能够被多个线程同时访问的资源,如读多写少的数据结构或资源池的管理。
AQS定义的两种队列
1. Condition Queue (条件队列)
条件队列用于管理在条件变量上等待的线程。在 AQS 中,条件队列是通过 Condition
接口来实现的。Condition
接口允许线程在某个条件不满足时等待,直到另一个线程通知它条件已经满足。
在 AQS 的实现中,每个 Condition
对象都有一个关联的等待队列(条件队列),用于存放在该条件上等待的线程。这些线程会被放入条件队列中,并在条件满足或者被其他线程中断时被唤醒。
主要的方法包括:
-
await()
:将当前线程放入条件队列,并释放当前线程持有的锁,使其进入等待状态,直到被其他线程通过signal()
或signalAll()
方法唤醒。 -
signal()
:唤醒在条件队列中等待的一个线程,如果有多个线程在等待,则唤醒其中一个线程。 -
signalAll()
:唤醒在条件队列中等待的所有线程。
2. Wait Queue (等待队列)
等待队列用于管理在同步器上等待的线程。在 AQS 中,等待队列实际上是通过双向链表(又称 CLH 队列)来实现的。等待队列存放那些由于未获取到锁而需要进入等待状态的线程节点。
等待队列中的每个节点通常包含一个或多个等待状态,表示线程在等待获取锁时的状态信息。当一个线程尝试获取锁失败时,它会被封装成一个节点并加入到等待队列中,然后进入休眠状态。
主要的方法包括:
-
acquire()
:尝试获取同步状态,如果获取失败则将当前线程加入等待队列中,并进入休眠状态。 -
release()
:释放同步状态,如果释放成功,则尝试唤醒等待队列中的下一个节点。
等待队列通过双向链表的结构有效地管理了等待获取同步状态的线程,保证了线程的公平竞争和高效的唤醒机制。
AQS 定义的5个队列中节点状态
-
CANCELLED (1):
- 节点状态为
CANCELLED
表示线程节点已经取消。通常情况下,这种状态是因为超时或者中断导致的。这些节点将不会再参与同步操作。
- 节点状态为
-
SIGNAL (-1):
- 节点状态为
SIGNAL
表示后继节点在释放时需要唤醒其前驱节点。即当前节点的线程在释放同步状态之后,会唤醒后继节点对应的线程,使其可以尝试获取同步状态。
- 节点状态为
-
CONDITION (-2):
- 节点状态为
CONDITION
表示线程节点在等待条件变量时被放入条件队列。在条件队列中等待的线程节点状态通常为CONDITION
。
- 节点状态为
-
PROPAGATE (-3):
- 节点状态为
PROPAGATE
用于共享模式同步。表示释放操作应该向后继节点传播。在共享模式下,如果当前节点释放了锁或者信号,需要将状态传播给后继节点。
- 节点状态为
-
0:
- 节点状态为
0
表示线程节点还未被唤醒或者加入队列。这是初始状态或者表示线程在队列中等待时的默认状态。
- 节点状态为
这些节点状态在 AQS 的实现中,通过 Node
类来表示,每个节点都有一个状态字段来标识其当前状态。这些状态对于实现不同的同步策略和队列管理至关重要,确保了线程在不同状态下的正确唤醒和管理。