深入浅出Java多线程(十一):AQS
引言大家好,我是你们的老店员秀才!今天带来的是[深入浅出Java多线程]系列的第十一篇内容:AQS(AbstractQueuedSynchronizer)。大家以为有效请点赞,喜欢请关注!秀才在此谢过大家了!!!
在现代多核CPU环境中,多线程编程已成为提拔系统性能和并发处置惩罚能力的关键本领。然而,当多个线程共享同一资源或访问临界区时,如何有效地控制线程间的实行顺序以保证数据一致性及避免竞态条件变得至关重要。Java平台为办理这些题目提供了多种同步机制,如synchronized关键字、volatile变量以及更加灵活且功能强大的并发工具类库——java.util.concurrent包。
在这一庞大的并发工具箱中,AbstractQueuedSynchronizer(简称AQS)扮演了核心角色。作为Java并发框架中的基石,AQS是一个高度抽象的底层同步器,它不仅被广泛应用于诸如ReentrantLock、Semaphore、CountDownLatch、CyclicBarrier等尺度同步组件,还为开发者提供了一种便捷的方式来构建符合特定需求的自界说同步器。
AQS的设计理念是基于模板方法模式,通过封装复杂的同步状态管理和线程排队逻辑,使得子类只需关注并实现资源获取与释放的核默算法即可。它使用一个名为state的volatile变量来表示同步状态,并借助于FIFO双端队列结构来管理等待获取资源的线程。AQS内部维护的Node节点不仅包含了每个等待线程的信息,而且还通过waitStatus标志位巧妙地实现了独占式和共享式的两种资源共享模式。
例如,在ReentrantLock中,AQS负责记录当前持有锁的线程重入次数,而当线程尝试获取但无法立刻得到锁时,会将该线程包装成Node节点并安全地插入到等待队列中。随后,线程会被优雅地阻塞,直至锁被释放或者其在等待队列中的位置变为可以获取资源的状态。这个过程涉及到一系列精心设计的方法调用,如tryAcquire(int)、acquireQueued(Node, int)和release(int)等。
// 示例代码:ReentrantLock基于AQS的简单应用<br>import java.util.concurrent.locks.ReentrantLock;<br><br>public class AQSExample {<br> private final ReentrantLock lock = new ReentrantLock();<br><br> public void criticalSection() {<br> lock.lock(); // 调用lock()即尝试获取AQS的资源<br><br> try {<br> // 临界区代码<br> System.out.println("Thread " + Thread.currentThread().getName() + " is executing critical section.");<br> } finally {<br> lock.unlock(); // 释放资源<br> }<br> }<br><br> public static void main(String[] args) {<br> AQSExample example = new AQSExample();<br> Thread t1 = new Thread(example::criticalSection, "Thread-1");<br> Thread t2 = new Thread(example::criticalSection, "Thread-2");<br><br> t1.start();<br> t2.start();<br> }<br>}<br><br>在这个简单的示例中,我们创建了一个ReentrantLock实例并在两个线程中分别调用lock方法进入临界区。如果第一个线程已经占有锁,第二个线程将会进入等待队列,直到锁被释放。这背后的机制正是由AQS提供的强大同步支持所驱动的。通过对AQS的深入探究,读者将能更好地理解这些高级同步工具的内部工作原理,从而更高效地举行并发编程实践。
AQS简介
在Java多线程编程中,AbstractQueuedSynchronizer(简称AQS)作为J.U.C包下的一款核心同步框架,扮演了构建高效并发锁和同步器的重要角色。AQS的设计理念与实现机制极大地简化了开发职员创建自界说同步组件的工作量,同时提供了强大的底层支持以满意多样化的并发控制需求。
队列管理: 从数据结构层面看,AQS内部维护了一个基于先进先出(FIFO)原则的双端队列。该队列并非直接存储线程对象,而是使用Node节点表示等待资源的线程,并通过volatile变量state记录当前资源的状态。AQS利用两个指针head和tail准确地跟踪队列的首尾位置,确保线程在无法立刻获取资源时可以大概安全且有序地进入等待状态。
同步功能: AQS不仅实现了对资源的原子操作,例如通过getState()、setState()以及基于Unsafe的compareAndSetState()方法保证资源状态更新的原子性和可见性,还提供了线程排队和阻塞机制,包括线程等待队列的维护、入队与出队的逻辑,以及线程在资源未得到时如何正确地挂起和唤醒等核心功能。
应用实例: AQS的强大之处在于它支撑了许多常见的并发工具类,诸如ReentrantLock、Semaphore、CountDownLatch、ReentrantReadWriteLock以及SynchronousQueue等,这些同步工具均是建立在AQS基础之上的,有效地办理了多线程环境下的互斥访问、信号量控制、倒计数等待、读写分离等多种同步题目。
下面是一个简单的代码示例,展示了如何使用基于AQS实现的ReentrantLock举行线程同步:
import java.util.concurrent.locks.ReentrantLock;<br><br>public class AQSExample {<br> private final ReentrantLock lock = new ReentrantLock();<br><br> public void criticalSection() {<br> lock.lock(); // 调用lock()方法尝试获取AQS管理的资源<br><br> try {<br> // 执行临界区代码<br> System.out.println("Thread " + Thread.currentThread().getName() + " is in the critical section.");<br> } finally {<br> lock.unlock(); // 在finally块中确保资源始终会被释放<br> }<br> }<br><br> public static void main(String[] args) {<br> AQSExample example = new AQSExample();<br> Thread t1 = new Thread(example::criticalSection, "Thread-1");<br> Thread t2 = new Thread(example::criticalSection, "Thread-2");<br><br> t1.start();<br> t2.start();<br> }<br>}<br><br>在这个例子中,当一个线程调用lock方法并成功获取到资源(即得到锁)时,另一个线程必须等待直至锁被释放。这一过程正是通过AQS所维护的线程等待队列和相应的同步算法得以实现的。别的,AQS也支持资源共享的两种模式,即独占模式(一次只有一个线程能获取资源)和共享模式(允许多个线程同时获取资源但数量有限制),而且灵活地支持可中断的资源请求操作,为复杂多样的并发场景提供了一站式的办理方案。
AQS的数据结构
在Java多线程编程中,AbstractQueuedSynchronizer(AQS)的数据结构设计是其高效实现同步功能的关键。AQS的核心数据结构主要包括以下几个部分:
volatile变量state:AQS内部维护了一个名为state的volatile整型变量,用于表示共享资源的状态。该状态值可以用来反映资源的数量、锁的持有状态等信息,具体含义由基于AQS构建的具体同步组件界说。由于state是volatile修饰的,因此确保了对它的修改能被其他线程及时看到,实现了跨线程的内存可见性。
protected volatile int state;<br><br>Node双端队列:AQS使用一个FIFO(先进先出)的双端队列来存储等待获取资源的线程。这里的节点并非直接存储线程对象,而是封装为Node类的对象,每个Node代表一个等待线程,并通过prev和next指针形成链表结构。头尾指针head和tail分别指向队列的首尾结点,便于举行快速插入和移除操作。
static final class Node {<br> volatile int waitStatus;<br> volatile Node prev;<br> volatile Node next;<br> volatile Thread thread;<br> // 其他成员方法及属性...<br>}<br><br>waitStatus标志位:每个Node节点都有一个waitStatus字段,它是一个int类型的volatile变量,用以标识当前节点所对应的线程等待状态。例如,CANCELLED表示线程已经被取消,SIGNAL表示后继节点的线程必要被唤醒,CONDITION则表示线程在条件队列中等待某个条件满意,另有如PROPAGATE如许的状态值用于共享模式下的资源流传。
线程调度逻辑:当线程尝试获取资源失败时,会创建一个Node节点并将当火线程包装进去,然后利用CAS算法将其安全地加入到等待队列的尾部。而在释放资源时,AQS会根据资源管理策略从队列中选择合适的节点并唤醒对应线程。
资源共享模式支持:AQS内建了对独占模式和共享模式的支持,这两种模式的区别在于:独占模式下同一时候只能有一个线程获取资源,典范的如ReentrantLock;而共享模式允许多个线程同时获取资源,如Semaphore和CountDownLatch。在Node节点的设计上,通过SHARED和EXCLUSIVE静态常量区分不同模式的节点。
尽管AQS提供了如tryAcquire(int)、tryRelease(int)等方法供子类覆盖以完成特定的资源控制逻辑,但具体的线程入队与出队、状态更新以及阻塞与唤醒等底层细节都是由AQS自己精心设计并实现的。这种机制使得基于AQS构建的同步工具可以大概有效地处置惩罚并发场景中的竞争题目,保证了线程间的安全协同实行。遗憾的是,由于篇幅限制,在此处无法提供完整的代码示例来展示AQS如何将线程包装成Node节点并维护其在线程等待队列中的位置变革。
总结AQS的数据结构如下图:
https://img2024.cnblogs.com/blog/3378408/202403/3378408-20240312134535747-1341695321.png
资源共享模式
在Java多线程同步框架AbstractQueuedSynchronizer(AQS)中,资源共享模式是其核心概念之一,用于界说并发环境中资源的访问方式。AQS支持两种主要的资源共享模式:独占模式(Exclusive)和共享模式(Share)。
独占模式:在独占模式下,同一时间只能有一个线程获取并持有资源,典范的例子就是ReentrantLock。当一个线程成功获取锁之后,其他试图获取锁的线程将被阻塞,直到持有锁的线程释放资源。通过AQS中的tryAcquire(int)方法实现对资源的尝试获取,以及tryRelease(int)方法来释放资源。例如:
import java.util.concurrent.locks.ReentrantLock;<br><br>public class ExclusiveModeExample {<br> private final ReentrantLock lock = new ReentrantLock();<br><br> public void criticalSection() {<br> lock.lock(); // 尝试以独占模式获取资源(即获取锁)<br><br> try {<br> // 在这里执行临界区代码<br> } finally {<br> lock.unlock(); // 释放资源(即释放锁)<br> }<br> }<br><br> public static void main(String[] args) {<br> ExclusiveModeExample example = new ExclusiveModeExample();<br> Thread t1 = new Thread(example::criticalSection, "Thread-1");<br> Thread t2 = new Thread(example::criticalSection, "Thread-2");<br><br> t1.start();<br> t2.start();<br> }<br>}<br><br>在这个示例中,两个线程尝试进入临界区,但由于使用的是ReentrantLock(基于AQS),因此在同一时候仅允许一个线程实行临界区代码。
共享模式:而在共享模式下,多个线程可以同时获取资源,但通常会限制可同时访问资源的线程数量。Semaphore和CountDownLatch就是采取共享模式的例子。例如,在Semaphore中,可以通过参数指定允许多少个线程同时访问某个资源:
import java.util.concurrent.Semaphore;
public class SharedModeExample {
private final Semaphore semaphore = new Semaphore(3); // 只允许最多3个线程同时访问资源
public void accessResource() {
try {
semaphore.acquire(); // 获取许可,如果当前可用许可数小于1,则线程会被阻塞
// 在这里实行必要保护的共享资源操作
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
} finally {
semaphore.release(); // 释放许可,使其他等待的线程有机会继续访问
}
}
public static void main(String[] args) {
SharedModeExample example = new SharedModeExample();
for (int i = 0; i
页:
[1]