【JUC】三十、什么是AQS
AQS,即AbstractQueuedSynchronizer,抽象的队列同步器。AQS是JUC的基石,好比JVM之于Java。AQS是用来实现锁或者其它同步器组件的公共基础部分的抽象实现, 是重量级基础框架及整个JUC体系的基石,主要用于解决锁分配给"谁"的问题。AbstractOwnableSynchronizer(下面两兄弟的父类)AbstractQueuedSynchronizer:简称A
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值的修改。(CAS改State值保证了多个线程同时去抢资源锁时的原子性)
6、公平锁与非公平锁
基于AQS框架开发锁,这个锁是公平锁还是非公平锁,区别就在:如下,线程0执行后,正好新进来一个线程5,如果:
- 新来的线程与队列中的线程共同抢锁(抢着去CAS改state),那就是非公平锁
- 新来的线程排到了FIFO队列的末尾,不参与抢锁,只让队列中的head线程获得锁,那就是公平锁
7、state和CLH队列
AQS类、Node类以及JUC相关类的关联关系如下:
AQS类,有个int类型的成员变量state:
private volatile int state;
它就类似去银行办理业务的受理窗口状态。0就是没人,自由状态可办理,大于等于1就是有人占用窗口,其他人坐在候客区(队列)的椅子上(Node)等着去。而CLH队列,一个双向队列,就是候客区,队列中装的就是一个个Node内部类的对象。
总结:AQS的实质就是CLH的双端队列,加一个state变量,队列中排队的每个个体就是一个Node。
8、AQS的内部类Node
Node的成员变量含义:
static final class Node {
/** 共享*/
static final Node SHARED = new Node();
/** 独占 */
static final Node EXCLUSIVE = null;
/** 线程被取消了,即排队在Node里的线程不想排了 */
static final int CANCELLED = 1;
/** 后继线程需要被唤醒*/
static final int SIGNAL = -1;
/** 等待condition唤醒 */
static final int CONDITION = -2;
/**共享式同步状态获取将会无条件地传播下去*/
static final int PROPAGATE = -3;
/**初始为0,状态是上面的1,-1,-2,-3这几种
* 代表Node里面线程的等待状态,其取值不同,后面的出队入队也就跟着受影响,和AQS的state是两个东西
*/
volatile int waitStatus;
/**当前node的前置节点*/
volatile Node prev;
/**当前node的后置节点
*/
volatile Node next;
/**坐node这个椅子里的线程*/
volatile Thread thread;
/** 构造方法、实例方法、静态代码块略*/
}
开放原子开发者工作坊旨在鼓励更多人参与开源活动,与志同道合的开发者们相互交流开发经验、分享开发心得、获取前沿技术趋势。工作坊有多种形式的开发者活动,如meetup、训练营等,主打技术交流,干货满满,真诚地邀请各位开发者共同参与!
更多推荐
所有评论(0)