Java基础知识AQS

101 阅读5分钟

什么是AQS?

AbstractQueuedSynchronizer抽象类是核心,它提供了一个基于FIFO队列,可以用于构建锁或者其他相关同步装置的基础框架。

image.png

AQS的核心思想

当线程请求共享资源时,如果共享资源是空闲的,则将共享资源锁定; 如果共享资源已经被占用,则当前线程放入到阻塞等待队列中,等待共享资源被释放时唤醒。

实现原理

  1. AQS使用一个int(volatile修饰保障可见性)成员变量来表示同步状态;通过方法getState,setState,compareAndSetState进行操作
  2. 通过内置的FIFO队列来完成获取资源线程的排队工作;
  3. AQS使用CAS对该同步状态进行原子操作实现对其值的修改;底层使用UnSafe,用于执行原子性的CAS操作。

底层的睡眠和唤醒机制:LockSupport.park()和LockSupport.unPark();

AQS 对资源的共享方式

  1. 独占式(Exclusive): 有一个线程能执行,如ReentrantLock。又可分为公平锁和非公平锁: 公平锁:按照线程在队列中的排队顺序,先到者先拿到锁 非公平锁:当线程要获取锁时,无视队列顺序直接去抢锁,谁抢到就是谁的
  2. Share(共享):多个线程可同时执行,如Semaphore、CountDownLatCh、 CyclicBarrier、ReadWriteLock

两者实现的差异,就在于底层都继承Node,在加锁时放入的队列是不一样的。

AQS底层使用了模板方法模式

  1. 使用者继承AbstractQueuedSynchronizer并重写指定的方法
  2. 将AQS组合在自定义同步组件的实现中,并调用其模板方法,而这些模板方法会调用使用者重写的方法。
isHeldExclusively()//该线程是否正在独占资源。只有用到condition才需要去实现它。
tryAcquire(int)//独占方式。尝试获取资源,成功则返回true,失败则返回false。
tryRelease(int)//独占方式。尝试释放资源,成功则返回true,失败则返回false。
tryAcquireShared(int)//共享方式。尝试获取资源。负数表示失败;0表示成功,但没有剩余可用资源;正数表示成功,且有剩余资源。
tryReleaseShared(int)//共享方式。尝试释放资源,成功则返回true,失败则返回false。

以ReentrantLock为例,state初始化为0,表示未锁定状态。 A线程lock()时,会调用tryAcquire()独占该锁并将state+1。 此后,其他线程再tryAcquire()时就会失败,直到A线程unlock()到state=0(即释放锁)为止,其它线程才有机会获取该锁。 当然,释放锁之前,A线程自己是可以重复获取此锁的(state会累加),这就是可重入的概念。 但要注意,获取多少次就要释放多少次,这样才能保证state是能回到零态的。

数据结构

AbstractQueuedSynchronizer类底层的数据结构是使用CLH队列是一个虚拟的双向队列(虚拟的双向队列即不存在队列实例,仅存在结点之间的关联关系)。 每一个结点都是由前一个结点唤醒.

CountDownLatch源码解析

  1. 底层Sync类继承AbstractQueuedSynchronizer,初始化时的参数就是state的数量,state不能小于0;
  2. 核心方法:countDown(), 作用:减少state 本质上回调了自身的内部方法: tryRelease(int args); 底层使用UnSafe.compareAndSetInt()让state-1的效果;
  3. 核心方法:await(); 等待共享资源被全部释放; 阻塞等待; 如果线程被中断,会抛出InterruptedExeception();

ReentrantLock源码解析

  1. 底层3个类,Sync, NonfairSync(默认为非公平策略),FairSync
  2. 公平锁:每次加锁时,判断当前state=0时,等待队列中是否有线程,如果有,则加入到队尾;
  3. 非公平锁: 每次加锁时,判断当前state=0时,都会用CAS操作去抢占。

UnSafe类

Unsafe 类是 Java 内部提供的一个非常强大的工具类,它提供了对底层内存和系统资源的直接操作能力,绕过了 Java 的安全管理机制。 因此,使用 Unsafe 可以执行一些常规 Java 中不允许的操作,比如直接访问内存、修改对象字段、直接分配内存等。

问题: 为什么要从后往前找第一个非Cancelled的节点呢?

节点入队,也就是acquire(1)方法时,如果state已经被占用,就会加入等待队列; 如果是从前往后找,由于极端情况下入队的非原子操作和CANCELLED节点产生过程中断开Next指针的操作,可能会导致无法遍历所有的节点

AQS的应用场景

同步工具同步工具与AQS的关联
ReentrantLock使用AQS保存锁重复持有的次数。当一个线程获取锁时,ReentrantLock记录当前获得锁的线程标识,用于检测是否重复获取,以及错误线程试图解锁操作时异常情况的处理。
Semaphore使用AQS同步状态来保存信号量的当前计数。tryRelease会增加计数,acquireShared会减少计数。
CountDownLatch使用AQS同步状态来表示计数。计数为0时,所有的Acquire操作(CountDownLatch的await方法)才可以通过。
ReentrantReadWriteLock使用AQS同步状态中的16位保存写锁持有的次数,剩下的16位用于保存读锁的持有次数。
ThreadPoolExecutorWorker利用AQS同步状态实现对独占线程变量的设置(tryAcquire和tryRelease)。