https://zhuanlan.zhihu.com/p/370501087
1 什么是AQS
AQS(AbstractQueuedSynchronizer)是 Java 中实现锁和同步器的基础设施,它是一个抽象类,提供了构建锁和同步器的基本框架,可以用于实现 ReentrantLock、Semaphore、CountDownLatch 等同步器。A Q S定义了一套多线程访问共享资源的同步模板,解决了实现同步器时涉及的大量细节问题,能够极大地减少实现工作。
2 AQS结构
state同步状态、Node组成的CLH队列、ConditionObject条件变量(包含Node组成的条件单向队列)。
2.1 同步状态
对于A Q S来说,线程同步的关键是对state的操作,可以说获取、释放资源是否成功都是由state决定的,比如state>0代表可获取资源,否则无法获取,所以state的具体语义由实现者去定义,现有的ReentrantLock、ReentrantReadWriteLock、Semaphore、CountDownLatch定义的state语义都不一样。
- ReentrantLock的state用来表示是否有锁资源
- ReentrantReadWriteLock的state高16位代表读锁状态,低16位代表写锁状态
- Semaphore的state用来表示可用信号的个数
- CountDownLatch的state用来表示计数器的值
2.2 CLH队列
CLH是AQS内部维护的FIFO(先进先出)双端双向队列(方便尾部节点插入),基于链表数据结构,当一个线程竞争资源失败,就会将等待资源的线程封装成一个Node节点,通过CAS
原子操作插入队列尾部,最终不同的Node节点连接组成了一个CLH队列,所以说AQS通过CLH队列管理竞争资源的线程。
在AQS的实现中,如果线程在获取锁时发现锁已经被占用,那么此时线程将进入自旋
等待。自旋等待的次数由重入次数和内部逻辑决定,如果自旋等待时间过长或获取不到锁,线程将转入阻塞状态。通过 acquire() 和 release() 接口向队列中添加或移除等待线程。
- acquire() 中,AQS 会使用 CAS 操作来尝试获得锁,如果获得失败,则会将当前线程加入到等待队列中
- release() 中,AQS 则会唤醒等待队列中的线程,使其有机会再次尝试获取锁。
什么是CAS
?
CAS(Compare-and-Swap)是一种原子性操作,用于实现并发编程中的同步机制。CAS 操作可用于多线程之间进行协作,确保只有一个线程可以对某个共享变量进行修改。
期望对象为某个值并设置为新的值。那么,如果不为期望的值或更新值失败,返回false;如果为期望的值并且设置成功,那么返回true。CAS 操作通常包含三个参数:
- 内存地址 V:用于指示需要更新的值的内存地址。
- 预期值 A:用于指示存储在 V 中的预期值,即当前值。
- 新值 B:用于指示需要将 V 更新为的新值。
什么是自旋
?
自旋是一种在线程等待锁或资源时的暂停方式。当一个线程在获取锁或者等待资源时,如果发现资源被占用并不会立即被释放,那么该线程将不会进入阻塞状态,而是一直循环询问资源状态是否可用,此时该线程就处于自旋状态,循环执行一段无意义的指令,直到获取到锁或资源后再继续执行下去。
自旋可以减少线程的阻塞和唤醒的次数,从而减少线程上下文切换的开销,提高整个系统的运行效率。因为当线程竞争的资源较少时,自旋等待是比较有效的,而当资源竞争比较激烈时,自旋等待会导致大量的资源浪费,此时需要使用其他方式进行线程等待。
需要注意的是,自旋等待不适用于所有场景。当线程等待时间较长时,自旋等待可能会显著影响系统性能,此时可以使用线程阻塞或其他方式实现等待。此外,自旋等待还可能会导致CPU资源的浪费,因此需要根据实际情况进行选择和使用。
CLH队列的优点
- 先进先出保证了公平性
- 非阻塞的队列,通过自旋锁和C A S保证节点插入和移除的原子性,实现无锁快速插入
- 采用了自旋锁思想,所以CLH也是一种基于链表的可扩展、高性能、公平的自旋锁
- 由于每个线程只需要关注自己的前驱节点,因此减少了不必要的锁竞争,提高了锁竞争的效率
- 同时,CLH队列通过自适应的方式来扩展队列,可以根据需要动态分配、释放队列节点,且不会出现竞争情况。
2.3 Node
Node是A Q S的内部类,每个等待资源的线程都会封装成Node节点组成C L H队列、等待队列。
3 AQS流程
线程获取资源失败,封装成Node节点从C L H队列尾部入队并阻塞线程,某线程释放资源时会把C L H队列首部Node节点关联的线程唤醒,再次获取资源。