AQS思想

2023-10-16 10:21:59 浏览数 (1)

什么是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,这说明我们加锁是成功的。

0 人点赞