1. AQS简介:
AQS是什么:
AQS全名:AbstractQueuedSynchronizer即抽象的队列同步器,这个类在java.util.concurrent.locks包下面。它实现了一个FIFO(FirstIn、FisrtOut先进先出)的队列。AQS底层实现的数据结构是一个双向链表。
AQS抽象队列同步器:
抽象的队列同步器, 用来构建锁或者其他同步器组件的重量级基础框架及整个JUC体系的基石,通过内置的FIFO队列来完成资源获取线程的排队工作,并通过一个int类型变量表示持有锁的状态;
- 抽象: 符合模板设计模式,作为核心父类给子类继承
- 队列: 对抢占不到锁的管理
- 同步器: 队列的排队的线程进行管理
AQS作用:加锁会导致阻塞,有阻塞就会排队,实现排队必然需要有某种形式的队列来进行管理:
-
AQS是一个用来构建锁和同步器的框架,使用AQS能简单且高效地构造出应用广泛的大量的同步器,比如我们提到的ReentrantLock,Semaphore,CountDownLatch,其他的诸如ReentrantReadWriteLock,SynchronousQueue,LinkedBlockingQueue等,FutureTask等等皆是基于AQS的。当然,我们自己也能利用AQS非常轻松容易地构造出符合我们自己需求的同步器。
LinkedBlockingQueue是线程安全的,那就要解决互斥和同步的问题,这一点我们可以通过Java中提供的锁来解决。Java中锁分两大类,一类是synchronized实现的隐式锁,另一类是并发编程大师Doug Lea基于AQS实现的锁。由于LinkedBlockingQueue是Doug Lea所编写的类,因此LinkedBlockingQueue底层使用的是AQS类型的锁,即:ReentrantLock。
-
ReentrantLock底层使用了CAS+AQS队列实现,所以我们基于ReentrantLock的一些线程安全的阻塞队列(如ArrayBlockingQueue ,LinkedBlockingQueue ,PriorityBlockingQueue ,DelayQueue,SynchronousQueue,LinkedBlockingDeque)的最底层都是AQS;
-
是用来构建锁或者其它同步器组件的重量级基础框架及整个JUC体系的基石, 通过内置的FIFO队列来完成资源获取线程的排队工作,并通过一个int类变量state 表示持有锁的状态;
2. AQS的原理:
AQS核心思想是:
- 如果被请求的共享资源空闲,则将当前请求资源的线程设置为有效的工作线程,并且将共享资源设置为锁定状态。
- -如果被请求的共享资源被占用,那么就需要一套线程阻塞等待以及被唤醒时锁分配的机制,这个机制AQS是用CLH队列锁实现的,即将暂时获取不到锁的线程加入到队列中。
CLH(Craig,Landin,and Hagersten)队列是一个虚拟的双向队列(虚拟的双向队列即不存在队列实例,仅存在结点之间的关联关系)。AQS是将每条请求共享资源的线程封装成一个CLH锁队列的一个结点(Node)来实现锁的分配。
AQS为实现阻塞锁,依赖先进先出的一个等待,依靠一个原子int值的state来表示状态,通过占用和释放方法,改变状态值AQS使用一个volatile的int类型的成员变量来表示同步状态,通过内置的FIFO队列来表示完成获取资源的排队工作将每条要去抢占资源的线程封装成一个Node节点来实现锁的分配,通过CAS完成对State值得修改;
看个AQS(AbstractQueuedSynchronizer)原理图:AQS本质是一个状态变量+队列
AQS使用一个int成员变量state来表示同步状态(state为0表示没有被占用,大于0表示被占用),通过内置的FIFO队列来完成获取资源线程的排队工作。AQS使用CAS对该同步状态进行原子操作实现对其值的修改。
private volatile int state;//共享变量,使用volatile修饰保证线程可见性
3. 和AQS相关的类:
ReentrantLock底层是Sync类–>Sync类继承了AbstractQueuedSynchronizer:
CountDownLatch底层也是Sync类–>Sync继承了AbstractQueuedSynchronizer:
private static final class Sync extends AbstractQueuedSynchronizer
可重入的读写锁:
ReentrantReadWriteLock 中也有一个内部类Sync–>Sync类继承了 AbstractQueuedSynchronizer:
信号灯类:Semaphore中的内部类Sync extends AbstractQueuedSynchronizer
总结:和AQS有关的类:
理解锁和同步器的关系:
锁,就是API方面写好的现成的锁,如ReentrantReadWriteLock,ReentrantLock等,我们直接拿来用。
①锁,面向锁的使用者:定义了程序员和锁交互的使用层API,隐藏了实现细节,你调用即可。
②同步器,面向锁的实现者:比如Java并发大神Douglee,提出统一规 范并简化了锁的实现,屏蔽了同步状态管理、阻塞线程排队和通知、唤醒机制等。
AQS能干嘛:
加锁会导致阻塞,有阻塞就需要排队,实现排队必然需要有某种形式的队列来进行管理。
1.抢到资源的线程直接使用办理业务,抢占不到资源的线程的必然涉及一种排队等候机制,抢占资源失败的线程继续去等待(类似办理窗口都满了,暂时没有受理窗口的顾客只能去候客区排队等候),仍然保留获取锁的可能且获取锁流程仍在继续(候客区的顾客也在等着叫号,轮到了再去受理窗口办理业务).
2.既然说到了排队等候机制,那么就一定会有某种队列形成,这样的队列是什么数据结构呢?
3.如果共享资源被占用,就需要一定的阻塞等待唤醒机制来保证锁分配。这个机制主要用的是CLH队列的变体实现的,将暂时获取不到锁的线程加入到队列中,这个队列就是AQS的抽象表现。它将请求共享资源的线程封装成队列的结点(Node) ,通过CAS、自旋以及LockSuport.park()的方式,维护state变量的状态,使并发达到同步的效果。
AQS初步:
AQS使用一个volatile的int类型的成员变量state来表示同步状态,通过内置的 FIFO队列来完成资源获取的排队工作将每条要去抢占资源的线程封装成 一个Node节点来实现锁的分配,通过CAS完成对state值的修改。
hashMap中的k,v键值对 本质是放在一个Node<k,v>节点中的,而不是直接放进Map中的:
同理,我们的AQS中同样也有一个内部类节点Node<Thread>
,用于存放线程,此时Node<Thread>
是一个载体:AQS中装的是Node:
AQS内部体系架构:
看源码可知道:
可重入锁类ReentrantLock 实现了 Lock接口;
可重入锁类ReentrantLock 中 有三个内部类:Sync+NonFairSync+FairSync;
Sync内部类 又是其他 两个内部类(NonFairSync+FairSync)的父类;
Sync内部类 又是一个单独抽象类AbstractQueuedSynchronizer的子类;
1.AQS自身:
①AQS的int变量state:
AQS的同步状态state成员变量,可以理解为银行办理业务的受理窗口状态,零就是没人(锁是空闲的),自由状态可以办理。大于等于1(锁被占用),有人占用窗口,就需要去排队等待。
/*** The synchronization state.*/
private volatile int state;
②AQS的CLH队列:
CLH队列(三个大牛的名字组成),为一个双向队列。可以理解为银行的候客区。
小总结:有阻塞就需要排队,实现排队必然需要队列。(state变量+CLH双端Node队列)
2.内部类Node(Node类在AQS类内部):
①Node的int变量waitStatus:
Node的等待状态waitStatus。可以理解为队列中每个排队的个体就是一个Node,这个变量就是等候区其它顾客(其它线程)的等待状态。
②Node此类的讲解:
最终的方法落地到这里两个方法:
代码演示:
总结:
释放锁源码: