什么是AQS
AQS ( Abstract Queued Synchronizer )是一个抽象的队列同步器,通过维护一个共享资源状态( Volatile Int State )和一个先进先出( FIFO )的线程等待队列来实现一个多线程访问共享资源的同步框架。
AQS的核心思想
AQS核心思想是,如果被请求的共享资源空闲,则将当前请求资源的线程设置为有效的工作线程,并且将共享资源设置为锁定状态。如果被请求的共享资源被占用,那么就需要一套线程阻塞等待以及被唤醒时锁分配的机制,这个机制AQS是用CLH队列锁实现的,即将暂时获取不到锁的线程加入到队列中。 CLH(Craig,Landin,and Hagersten)队列是一个虚拟的双向队列(虚拟的双向队列即不存在队列实例,仅存在结点之间的关联关系)。AQS是将每条请求共享资源的线程封装成一个CLH锁队列的一个结点(Node)来实现锁的分配。 AQS使用一个int成员变量来表示同步状态,通过内置的FIFO队列来完成获取资源线程的排队工作。AQS使用CAS对该同步状态进行原子操作实现对其值的修改。
AQS中的同步器
AQS的全称为(AbstractQueuedSynchronizer)抽象的队列式的同步器,是⼀个⽤来构建锁和同步器的框架,使⽤AQS能简单且⾼效地构造出应⽤⼴泛的⼤量的同步器,如:基于AQS实现的lock, CountDownLatch、CyclicBarrier、Semaphore需解决的问题:
- 状态的原子性管理
- 线程的阻塞与解除阻塞
- 队列的管理
CountDownLatch:
通过计数法(倒计时器),让一些线程堵塞直到另一个线程完成一系列操作后才被唤醒;该⼯具通常⽤来控制线程等待,它可以让某⼀个线程等待直到倒计时结束,再开始执⾏。具体可以使用countDownLatch.await()来等待结果。多用于多线程信息汇总。
CompletableFuture:
通过设置参数,可以完成CountDownLatch同样的多平台响应问题,但是可以针对其中部分返回结果做更加灵活的展示。
CyclicBarrier:
字面意思是可循环(Cyclic)使用的屏障(Barrier)。他要做的事情是,让一组线程到达一个屏障(也可以叫同步点)时被阻塞,直到最后一个线程到达屏障时,屏障才会开门,所有被屏障拦截的线程才会继续干活,线程进入屏障通过CyclicBarrier的await()方法。可以用于批量发送消息队列信息、异步限流。
Semaphore:
信号量主要用于两个目的,一个是用于多个共享资源的互斥作用,另一个用于并发线程数的控制。SpringHystrix限流的思想
AQS案例
上面讲述的原理还是太抽象了,那我我们上示例,结合案例来分析AQS 同步器的原理。以ReentrantLock使用方式为例。 代码如下:
代码语言:javascript复制public class AQSDemo {
private static int num;
public static void main(String[] args) {
ReentrantLock lock = new ReentrantLock();
new Thread(new Runnable() {
@Override
public void run() {
lock.lock();
try {
Thread.sleep(1000);
num = 1000;
System.out.println("A 线程执行了1秒,num = " num);
} catch (InterruptedException e) {
e.printStackTrace();
}
finally {
lock.unlock();
}
}
},"A").start();
new Thread(new Runnable() {
@Override
public void run() {
lock.lock();
try {
Thread.sleep(500);
num = 500;
System.out.println("B 线程执行了0.5秒,num = " num);
} catch (InterruptedException e) {
e.printStackTrace();
}
finally {
lock.unlock();
}
}
},"B").start();
new Thread(new Runnable() {
@Override
public void run() {
lock.lock();
try {
Thread.sleep(100);
num = 100;
System.out.println("C 线程执行了0.1秒,num = " num);
} catch (InterruptedException e) {
e.printStackTrace();
}
finally {
lock.unlock();
}
}
},"C").start();
}
}
执行的某一种结果! 这个代码超级简单,但是执行结果却是可能不一样,大家可以自行实验。
对比一下这两种结果,大家会发现,无论什么样的结果,num最终的值总是1600,这说明我们加锁是成功的。