什么是AQS?
AQS 全称是 AbstractQueuedSynchronizer,是 Java 并发包中的一个抽象类,用于构建各种同步器和锁,如 ReentrantLock、CountDownLatch、Semaphore 、CountDownLatch、ThreadPoolExcutor 的 Worker 等都有运用。
参考:
https://juejin.cn/post/7273506068104478760
为什么需要AQS?
AQS实现了
- 状态的原子性管理
- 线程的阻塞与接触阻塞
- 队列的管理 AQS 是一个用于构建锁、同步器等线程协作工具类的框架,有了 AQS 以后,很多用于线程协作的工具类就都可以很方便的被写出来,有了 AQS 之后,可以让更上层的开发极大的减少工作量,避免重复造轮子,同时也避免了上层因处理不当而导致的线程安全问题,因为 AQS 把这些事情都做好了。总之,有了 AQS 之后,我们构建线程协作工具类就容易多了。
AQS内部原理是什么?
三大核心部分 状态、队列、期望协作工具类去实现的获取/释放等重要方法
1、state 状态
代码语言:java复制 /**
* The synchronization state.
*/
private volatile int state;
state AQS 想要去管理或者想作为协作工具类的一个基础框架,那么它必然要管理一些状态,而这个状态在 AQS 内部就是用 state 变量去表示的
在不同实现类里面语义不同:
Semaphore中,state 表示的是剩余许可证的数量。
CountDownLatch 中,state 表示的是需要“倒数”的数量
在 ReentrantLock 中,state表示的是锁的占有情况,最开始是 0,表示没有任何线程占有锁;如果 state 变成 1,则就代表这个锁已经被某一个线程所持有了。
那为什么还会变成 2、3、4 呢?为什么会往上加呢?因为 ReentrantLock 是可重入的,同一个线程可以再次拥有这把锁就叫重入。如果这个锁被同一个线程多次获取,那么 state 就会逐渐的往上加,state 的值表示重入的次数。在释放的时候也是逐步递减
2、FIFO 等待队列
双向链表
head 头节点
tail 尾节点
队列头结点称为"哨兵节点",它不与任何线程关联。当一个线程竞争锁失败时,AQS会把当前线程以及等待状态信息构造成一个Node加入到同步队列中,同时再阻塞该线程。当获取锁的线程释放锁以后,会从队列中唤醒一个阻塞的节点(线程)
3、获取/释放方法
独占式
独占式实际就是时刻上只允许一个线程独占该资源,多线程竞争情况下也只能有一个线程获取同步状态成功
共享式
共享式就是允许多个线程同时获取一定的资源,比如信号量、读锁就是用共享式实现的
其实共享式与独占式流程类似,只是尝试获取同步状态的实现不同
怎么使用AQS?
继承
如果想使用 AQS 来写一个自己的线程协作工具类,通常而言是分为以下三步,这也是 JDK 里利用 AQS 类的主要步骤:
第一步,新建一个自己的线程协作工具类,在内部写一个 Sync 类,该 Sync 类继承 AbstractQueuedSynchronizer,即 AQS;
第二步,想好设计的线程协作工具类的协作逻辑,在 Sync 类里,根据是否是独占,来重写对应的方法。如果是独占,则重写 tryAcquire 和 tryRelease 等方法;如果是非独占,则重写 tryAcquireShared 和 tryReleaseShared 等方法;
第三步,在自己的线程协作工具类中,实现获取/释放的相关方法,并在里面调用 AQS 对应的方法,如果是独占则调用 acquire 或 release 等方法,非独占则调用 acquireShared 或 releaseShared 或 acquireSharedInterruptibly 等方法。