文章目录
- 0、背景
- 1、AQS介绍
- 2、AQS核心概念
- 3、AQS是JUC的基石
- 4、锁和同步器的关系
- 5、AQS的作用
- 6、state和CLH队列
- 6、AQS的内部类Node
0、背景
一段常见的代码:
Lock lock = new ReentrantLock();
lock.lock;
try{
//do Something
} finally{
lock.unlock();
}
简单的一个加锁解锁,多线程来抢锁,抢不到锁的线程被分配到哪儿去了?放到一个队列中,后续再出对入队?队列底层又是怎么维护的?
1、AQS介绍
AQS,即AbstractQueuedSynchronizer,抽象的队列同步器。AQS是JUC的基石,好比JVM之于Java。
AQS是用来实现锁或者其它同步器组件的
公共基础部分
的抽象实现, 是重量级基础框架及整个JUC体系的基石,主要用于解决锁分配给"谁"的问题。
AQS相关类是:
- AbstractOwnableSynchronizer(下面两兄弟的父类)
- AbstractQueuedLongSynchronizer:since1.6
- AbstractQueuedSynchronizer:简称AQS,since1.5
三者都是抽象类。(抽象类,相比接口的全抽象,属半抽象,完成了一部分逻辑,剩余一部分定义了方法规范,给子类去继承和重写)
2、AQS核心概念
以下是AQS类的解释,关键词:先进先出的一个队列 + state状态值
类比人(线程)去银行(共享资源对象)排队办理业务,有人到窗口开始办时,指示灯变红(state=1,getState、setState),代表有人,其余人去候客区的椅子上等着:
这个候客区,整体就是一个抽象的FIFO队列来完成对获取资源线程的排队工作,并通过一个int类变量表示持有锁的状态
这个抽象队列,称为CLH队列,CLH即Craig、Landin and Hagersten,是三个人名,是一个单向链表,但AQS中的队列是CLH变体的虚拟双向队列FIFO,等待的线程就进入这个队列。
3、AQS是JUC的基石
之所以成AQS是JUC的基石,是因为JUC的很多东西底层都是通过AQS实现的,如图:
举个例子,比如ReentrantLock:
4、锁和同步器的关系
锁:
- 面向普通开发者,给开发者用的
- 定义了开发者和锁交互使用的API,比如lock、unlock,隐藏了实现细节,调用即可
同步器:
- 面向锁的实现者,Java并发的缔造者
- 提出统一规范并简化了锁的实现,屏蔽了同步状态管理、同步队列的管理和维护、阻塞线程排队和通知、唤醒机制等公共的底层细节,将其抽象出来,形成抽象的公共基类,做为一切锁和同步组件实现的公共基础部分
5、AQS的作用
加锁会导致阻塞,有阻塞,抢不到的线程就需要排队,实现排队必然需要队列。类比银行办理业务的窗口满了,其他人去候客区等待,但等待的线程仍然保留了获取锁的可能,且获取锁的流程仍在继续(候客区的顾客也在等着被叫到号)。
如果共享资源被占用,就需要一定的阻塞、唤醒机制来保证锁分配。这个机制主要用的是CLH队列的变体实现的,将暂时获取不到锁的线程加入到队列中,这个队列就是AQS同步队列的抽象表现。它将要请求共享资源的线程及自身的等待状态封装成队列的结点对象 Node(候客区的一把把椅子),通过CAS自旋以及LockSupport.park()的方式,维护state变量的状态,使并发达到同步的效果。
AQS使用一个volatile的int类型的成员变量state来表示同步状态,通过内置的FIFO队列来完成资源获取的排队工作,将每个要去抢占资源的线程封装成一个Node节点来实现锁的分配,通过CAS完成对State值的修改。
6、state和CLH队列
AQS类、Node类以及JUC相关类的关联关系如下:
AQS类,有个int类型的成员变量state:
private volatile int state;
它就类似去银行办理业务的受理窗口状态。0就是没人,自由状态可办理,大于等于1就是有人占用窗口,其他人坐在候客区(队列)的椅子上(Node)等着去。而CLH队列,一个双向队列,就是候客区,队列中装的就是一个个Node内部类的对象。
总结:AQS的实质就是CLH的双端队列,加一个state变量,队列中排队的每个个体就是一个Node。
6、AQS的内部类Node
,Node也有一个属性,waitState,和state是两个东西
银行候客区的椅子,每个椅子做一个排队的请求线程
共享型的还是独占型的
1,即排队在Node里的线程不想排了,
-2:即后续线程需要被唤醒
-3:线程正在condition里面等待
-3:传播、广播、一块儿的全部通知
Node这个内部类的成员变量,代表Node里面线程的等待状态,其取值不同,后面的出队入队也就跟着受影响
当前Node节点的前置、后置节点,坐Node里的线程