java AQS
什么是AQS
AQS(AbstractQueuedSynchronizer,抽象队列同步器)是 Java 中并发控制的一种机制,位于 java.util.concurrent.locks 包下,它为构建锁、信号量等同步工具提供了一个框架。AQS 通过 队列 来管理多个线程之间的同步与竞争,利用 FIFO 队列 来顺序执行线程,并通过内部的状态变量(state)来控制资源的分配和访问。
为什么需要 AQS?
AQS 解决了 并发编程中线程同步和竞争 的一系列问题。具体来说,AQS 提供了一种高效、灵活的方式来管理线程的排队、同步和竞争,它的核心作用是通过维护一个 同步队列 和 状态变量 来协调多个线程对共享资源的访问。
AQS 的引入解决了以下问题:
线程排队管理:在高并发场景下,多个线程可能竞争同一个资源或锁。AQS 通过维护一个 FIFO 队列 来确保线程按照请求的顺序排队等待资源,避免了线程被无序唤醒或频繁切换造成的资源浪费。
锁的抽象:在 Java 中,锁机制如 ReentrantLock 和 Semaphore 等都依赖于 AQS 来实现。AQS 提供了一个通用的机制,让不同的锁机制可以复用相同的底层实现,而无需重复编写线程管理和同步控制代码。
线程阻塞和唤醒机制:AQS 通过内建的阻塞和唤醒机制,能够实现线程的高效挂起与唤醒。通过 ConditionObject 和 LockSupport,AQS 可以将线程挂起,避免不必要的 CPU 占用,直到条件满足时才唤醒。
非公平与公平锁:AQS 通过队列管理,可以方便地实现公平锁和非公平锁的不同策略。非公平锁会让新加入的线程有可能直接获取锁,而公平锁会按照队列的顺序逐一唤醒等待线程。
AQS 的核心机制以及底层原理
AQS 主要依赖以下几个关键组件来实现同步:
状态变量(state):AQS 通过 state 变量来表示同步器的当前状态。不同的同步器(如锁、信号量等)会根据自己的需求设定 state 的含义。例如,ReentrantLock 中的 state 表示当前锁是否被占用,state = 0 表示没有线程持有锁,state > 0 表示有线程持有锁。
双向队列:AQS 内部维护了一个 FIFO 队列(即 CLH 队列),用于管理等待获取资源的线程。线程在等待资源时会被加入到队列中,线程会按照队列的顺序获取资源。
线程阻塞与唤醒:AQS 提供了高效的线程阻塞与唤醒机制。线程在获取不到锁或资源时会被阻塞,直到资源可用或者条件满足时才会被唤醒。线程的挂起与唤醒通常通过 LockSupport.park() 和 LockSupport.unpark() 来实现。
总结
非公平锁:
使用ReentranLock加锁,以及其他自定义锁的时候,都是基于AQS实现的
当第一个线程获取锁的时候先判断state值,因为state值为0,所以直接获取锁,这时候另一个线程想获取lock锁,通过自旋和CAS判断state,如果state为0则获取锁,不是则加入双向队列,然后其他线程加入进行也是一样的,自旋CAS获取锁,这时候因为是ReentrantLock是非公平锁,也可以说AQS是非公平的,双向队列中的其他等待线程也在自旋+CAS获取锁,哪个抢到锁则哪个推出双向等待队列
公平锁:
如果修改为公平锁,则按队列顺序获取锁。例如有5个线程,后面的线程需要等待前面的释放锁之后在被唤醒(阻塞状态-》可执行状态)然后进行自旋+CAS获取锁
AQS的使用场景
自定义锁:
ReentrantLock:ReentrantLock 是一种可重入的互斥锁,它依赖于 AQS 来管理锁的获取和释放。AQS 提供了线程的排队、竞争和资源分配机制,使得 ReentrantLock 能够高效地管理锁的状态。
ReentrantReadWriteLock:ReentrantReadWriteLock 也使用 AQS 来实现读写锁。AQS 在这里管理读锁与写锁的状态以及线程的等待队列。
StampedLock:StampedLock 是 JDK 8 中引入的高级锁,它也依赖于 AQS 来实现与 ReentrantReadWriteLock 类似的读写锁功能。
任务协调和同步:
CountDownLatch:CountDownLatch 也是基于 AQS 实现的,它允许一个或多个线程等待其他线程完成某些操作之后再继续执行。它通过 AQS 的状态变量来控制计数器的减少和线程的阻塞与唤醒。
CyclicBarrier:CyclicBarrier 用于协调多个线程在某个点上同步,通过 AQS 的队列和状态变量实现线程的等待和触发。
控制并发访问:
Semaphore:Semaphore 是一个计数信号量,允许有限数量的线程访问某些资源。它通过 AQS 来管理可用信号量的数量,确保线程能够按顺序获取信号量并进行阻塞与唤醒。
下面举两个例子:
ReentrantLock
咱们使用ReentrantLock锁最多的方式就是tryLock
下面是ReetrantLock源代码
//tryLock调用sync.nonfairTryAcquire(1),传入参数1
public boolean tryLock() {
return sync.nonfairTryAcquire(1);
}
可以看到nonfairTryAcquire方法在抽象类Sync中并继承了AQS
像里面很多方法都使用的是AQS类中的方法,以及AQS继承类中的方法,例如setExclusiveOwnerThread和getExclusiveOwnerThread方法
Semaphore
Semaphore(信号量)是 Java 中的一种并发控制工具类,位于 java.util.concurrent 包中。信号量主要用于控制对共享资源的访问,并通过控制允许并发访问的线程数量来限制资源的竞争。它常用于实现资源池、限流等场景。
信号量的基本原理是通过一个计数器来控制同时可以访问共享资源的线程数量。每当一个线程成功获取信号量时,信号量的计数器减少;当一个线程释放信号量时,计数器增加。线程会在计数器大于 0 时才能获取信号量,计数器为 0 时线程会被阻塞,直到有线程释放信号量。
常用的方法
acquire以及release
@Override
public void run() {
try {
// 请求信号量,如果信号量可用,计数器减 1
semaphore.acquire();
System.out.println(Thread.currentThread().getName() + " is accessing the resource");
// 模拟操作
Thread.sleep(2000);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
} finally {
// 释放信号量,计数器加 1
semaphore.release();
System.out.println(Thread.currentThread().getName() + " has released the resource");
}
}
一下是Semaphore类的源代码
public void acquire() throws InterruptedException {
sync.acquireSharedInterruptibly(1);
}
使用了sync类中的acquireSharedInterruptibly方法
可以看见Semaphore类中有个抽象内部类继承了AQS