深入理解AQS(抽象队列同步器)
一、什么是AQS
AQS:用来构建锁或其他同步器组件的重量级基础框架及整个JUC体系的基石,通过内置的FIFO队列来完成资源获取线程的排队工作,并通过一个int类型变量表示持有锁的状态。如果共享资源被占用,就需要一定的阻塞等待唤醒机制来保证锁的分配。主要通过CLH队列的变体实现,将暂时获取不到锁的线程加入到队列中,这个队列就是AQS抽象的表现。它将请求共享资源的线程封装成队列的节点(Node),通过CAS自旋以及LockSupport.park()的方式,维护state变量的状态,使并发达到同步的控制效果。
CLH队列:Craig、Landin and Hagersten队列,是一个单向链表,AQS中的队列是CLH变体的虚拟双向队列,虚拟的双向队列即不存在队列实例,仅存在节点之间的关联关系。
java.util.concurrent.locks.AbstractOenableSynchronizer;
解释:是用来构建锁或者其他同步器组件的重量级基础框架及整个JUC体系的基石,通过内置的FIFO(先进先出)队列来完成资源获取线程的排队工作,并通过一个int类变量表示持有锁的状态
和AQS有关的类:
ReentrantLock:
CountDownLatch
ReentrantReadWriteLock:
Semaphore:
锁和AQS的关系:锁面向锁的使用者,定义了程序员和锁交互的使用层API,隐藏了实现细节
同步器AQS是锁的实现
二、AQS源码分析
AQS使用一个volatile的int类型的成员变量来表示同步状态,通过内置的 FIFO队列来完成资源获取的排队工作将每条要去抢占资源的线程封装成 一个Node节点来实现锁的分配,通过CAS完成对State值的修改。
AQS的同步状态state成员变量 零说明没有资源占用,自由状态可以办理 大于等于1 有线程占用资源
AQS的CLH队列
AQS同步队列的基本结构
AQS底层是怎么排队的 :是用LockSupport.park()来进行排队的
三、通过ReentrantLock中的非公平锁进行分析
主要是通过以下几个方法进行实现的:
通过银行办理业务的例子来深入了解:
三个线程A,B,C来银行窗口办理业务,服务窗口每次只能服务一个人,初识的时候窗口是没人的!
lock()
这边线程A拿到了去窗口办理业务的机会,通过compareAndSetState比较并交换,将AQS中变量state设置成1
acquire()
线程A已经占用了窗口,线程B只能走acquire这个方法
tryAcquire()
在来看acquire() 的方法,B线程返回false,取反的是true,往下面看下一个方法
addWaiter(Node.EXCLUSLVE)
完成B节点的入队,通过enq源码可以知道,B线程第一次并没有进行入队,到第二次循环才将B线程入队返回
acquireQueued(addWaiter(Node.EXCLUSIVE), arg)
B抢成功了去去窗口办理业务,抢占不成功就在等待,直到被唤醒。
unlock();
A 办完业务,准备离开,B上位