AQS详解


前言

AQS 的全称为 AbstractQueuedSynchronizer ,翻译过来的意思就是抽象队列同步器。这个类在 java.util.concurrent.locks 包下面。AQS为Java的并发包提供了强大的同步支持。通过内置的FIFO队列来完成资源获取线程的排队工作,并且利用一个被volatile关键字修饰的int类型的变量state表示同步状态。AQS 为构建锁和同步器提供了一些通用功能的实现,许多同步类实现都依赖于它,如常用的ReentrantLock、Semaphore等。
在这里插入图片描述

一、AQS原理

AQS核心思想是,如果被请求的共享资源空闲,则将当前请求资源的线程设置为有效的工作线程,并且将共享资源设置为锁定状态(独享模式下)。其他没有获取到资源的线程就进入阻塞队列,等待当前占用资源的线程释放资源后,继续尝试获取。这个线程阻塞等待以及被唤醒时锁分配的机制在AQS中是用CLH队列锁实现的,即将暂时获取不到锁的线程加入到阻塞队列中。
以下是AQS原理图:
在这里插入图片描述
AQS 使用 int 成员变量 state 表示同步状态,通过内置的 FIFO 线程等待/等待队列 来完成获取资源线程的排队工作。

1. state

state 变量由 volatile 修饰,保证了多线程环境下的可见性,用于展示当前临界资源的获锁情况。

    /**
     * The synchronization state.
     */
    private volatile int state;

AQS内部还提供了获取和修改state的方法.

注意,这里的方法都是final修饰的,意味着不能被子类重写!

    // 获取state
    protected final int getState() {
        return state;
    }
 
    // 修改state
    protected final void setState(int newState) {
        state = newState;
    }
 
    // Cas操作修改state的值
    protected final boolean compareAndSetState(int expect, int update) {
        return unsafe.compareAndSwapInt(this, stateOffset, expect, update);
    }

2. CLH双向队列

CLH 锁是对自旋锁的一种改进,是一个虚拟的双向队列(虚拟的双向队列即不存在队列实例,仅存在结点之间的关联关系),暂时获取不到锁的线程将被加入到该队列中。
AQS 将每条请求共享资源的线程封装成一个 CLH 队列锁的一个结点(Node)来实现锁的分配。在 CLH 队列锁中,一个节点表示一个线程,它保存着线程的引用(thread)、 当前节点在队列中的状态(waitStatus)、前驱节点(prev)、后继节点(next)。
在这里插入图片描述

二、资源共享方式

在AQS的框架中对于资源的获取有两种方式:

  1. 独占模式(Exclusive) :资源是独有的,每次只能一个线程获取,如ReentrantLock;
  2. 共享模式(Share) :资源可同时被多个线程获取,具体可获取个数可通过参数设定,如CountDownLatch倒计时器,Semaphore信号量,CyclicBarrier循环栅栏。

1. 独占模式

以ReentrantLock为例,其内部维护了一个state字段,用来标识锁的占用状态,初始值为0,当线程A调用lock()方法时,会尝试通过tryAcquire()方法(钩子方法)独占该锁,并将state值设置为1,如果方法返回值为true表示成功,false表示失败,失败后线程A被放入等待队列中(CLH队列),直到其他线程释放该锁。

钩子方法是一种被声明在抽象类中的方法,一般使用 protected 关键字修饰,它可以是空方法(由子类实现),也可以是默认实现的方法。模板设计模式通过钩子方法控制固定步骤的实现。

但需要注意的是,在线程A获取到锁后,在释放锁之前,自身可以多次获取该锁,每获取一次state加1,这就是锁的可重入性,这也说明ReentrantLock是可重入锁,在多次获取锁后,释放时要释放相同的次数,这样才能保证最终state为0,让锁恢复到未锁定状态,其他线程去尝试获取。
流程图如下:
在这里插入图片描述

2. 共享模式

多个线程可以同时执行。
常见的基于共享模式的同步工具类有三种:

  • Semaphore信号量
  • CountDownLatch倒计时器
  • CyclicBarrier循环栅栏

Semaphore信号量

synchronized 和 ReentrantLock 都是一次只允许一个线程访问某个资源,而Semaphore(信号量)可以用来控制同时访问特定资源的线程数量。

Semaphore 有两种模式:

  • 公平模式: 调用 acquire() 方法的顺序就是获取许可证的顺序,遵循 FIFO;
  • 非公平模式: 抢占式的。
    Semaphore 对应的两个构造方法如下:
public Semaphore(int permits) {
	//非公平模式
    sync = new NonfairSync(permits);
}

public Semaphore(int permits, boolean fair) {
	//当传入的fair值为true时,此时为公平模式
    sync = fair ? new FairSync(permits) : new NonfairSync(permits);
}

原理:
你可以将semaphore看作是一个许可证,只有拿到许可证(让许可证数量成功减1)的线程可以继续执行后续代码,而semaphore中permits的值就是许可证的数量,就是某个时刻最大运行线程的数量。

方法
semaphore.acquire():当线程调用该方法时,线程尝试获取许可证,如果 state > 0 的话,则表示可以获取成功,然后尝试使用 CAS 操作去修改 state 的值 state=state-1,执行成功的话说明拿到许可证了,可以执行后续代码;如果 state <= 0 的话,则表示许可证数量不足,获取失败。如果获取失败则会创建一个 Node 节点加入等待队列,挂起当前线程。
semaphore.release() :当线程调用该方法时,线程尝试释放许可证,并使用 CAS 操作去修改 state 的值 state=state+1。释放许可证成功之后,同时会唤醒等待队列中的一个线程。被唤醒的线程会重新尝试去修改 state 的值 state=state-1 ,如果 state > 0 则获取令牌成功,否则重新进入等待队列,挂起线程。

当使用synchronized导致并发度太低的话,单体架构情况下可以使用semaphore提高并发度。

CountDownLatch倒计时器

CountDownLatch 允许 count 个线程阻塞在一个地方,直至所有线程的任务都执行完毕。CountDownLatch 是一次性的,计数器的值只能在构造方法中初始化一次,之后没有任何机制可以再次对其设置值,当 CountDownLatch 使用完毕后,它不能再次被使用。

构造方法如下:

public CountDownLatch(int count) {
    if (count < 0) throw new IllegalArgumentException("count < 0");
    this.sync = new Sync(count);
}

原理:
当线程调用 countDown() 时,其实使用了tryReleaseShared方法以 CAS 的操作来减少 state,直至 state 为 0 。当 state 为 0 时,表示所有的线程都调用了 countDown 方法,那么在 CountDownLatch 上等待的线程就会被唤醒并继续执行。

CyclicBarrier循环栅栏

CyclicBarrier 和 CountDownLatch 非常类似,它也可以实现线程间的技术等待,但是它的功能比 CountDownLatch 更加复杂和强大。主要应用场景和 CountDownLatch 类似。

CountDownLatch 的实现是基于 AQS 的,而 CycliBarrier 是基于 ReentrantLock(ReentrantLock 也属于 AQS 同步器)和 Condition 的。

CyclicBarrier 的字面意思是可循环使用(Cyclic)的屏障(Barrier)。它要做的事情是:让一组线程到达一个屏障(也可以叫同步点)时被阻塞,直到最后一个线程到达屏障时,屏障才会开门,所有被屏障拦截的线程才会继续干活。l

它的原理和CountDownLatch倒计时器相似。但是CyclicBarrier 还提供一个更高级的构造函数 CyclicBarrier(int parties, Runnable barrierAction),用于在线程到达屏障时,优先执行 barrierAction,方便处理更复杂的业务场景。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值