根据ReentrantLock -- 解析AQS原理

java.util.concurrent.locks.AbstractQueuedSynchronizer

在这里插入图片描述

是什么

aqs,这是一个队列同步器框架,JUC中的公平锁、非公平锁、重入锁都是以aqs作为基础框架的,定义了加锁、释放锁,加共享锁等一些逻辑
AQS是一个抽象类,内部使用了一个FIFO的等待队列,用于多线程等待锁排队,通过state表示当前资源的加锁状态;
aqs是基础类,类中定义了模板方法,只需要实现对应的模板方法即可;aqs的作者是Doug Lea

aqs内部维护的双向队列,大致是这样的,其中,比较特殊的是:第一个节点对应的thread是null,链表中的第二个节点,才是第一个排队的线程;这里的意思是:第一个节点是正在执行的线程,无需排队;图中少画了waitStatus信息,每个节点都会有一个waitStatus信息,用来存储下一个节点的等待状态
在这里插入图片描述

AQS核心属性


volatile int waitStatus;
    0,这是初始化状态,新Node会处于这种状态,初始化状态
static final int CANCELLED =  1;
    因为超时或者中断,Node被设置为取消状态,被取消的Node不应该去竞争锁,只能保持取消状态不变,不能转换为其他状态,处于这种状态的Node会被踢出队列,被GC回收
static final int SIGNAL = -1;
    由当前节点在队列中的后一个节点将当前节点的waitStatus设置为-1,表示告诉当前节点,如果当前节点unLock之后,通知后面的节点执行锁竞争(通过unPark()来唤醒)
static final int CONDITION = -2;
    表示这个Node在条件队列中,因为等待某个条件而被阻塞
static final int PROPAGATE = -3;
    使用在共享模式头Node有可能处于这种状态, 表示锁的下一次获取可以无条件传播
    
    
volatile Node prev;
    表示队列中当前Node节点的前一个节点
    
volatile Node next;
    表示队列中当前node节点的后一个节点
    
volatile Thread thread;
    这个node持有的线程,在new Node()的时候,需要把线程传进去
    
Node nextWaiter;
    表示下一个等待condition的Node
    
private transient volatile Node head;
    FIFO队列中的头结点
    
private transient volatile Node tail;
    FIFO队列中的尾节点
    
private volatile int state;
同步状态,0表示未加锁;1表示有一个线程加锁,2表示有两个线程加锁,一般是重入锁的场景
getState():获取同步状态
setState():设置同步状态
compareAndSetState(): 利用CAS进行同步状态的设置
spinForTimeoutThreshold = 1000L; :线程自旋等待时间
private Node enq(final Node node) {
   }: 
	是将当前排队的node节点放到FIFO的队尾;如果队列为空,就在node节点前面创建一个空节点,然后将node节点放到队尾

AQS使用说明

1、aqs中的node节点在排队的时候,waitStatus是0,在下一个排队的节点进来的时候,会把上一个节点的waitStatus设置为-1
2、笔记中说的aqs阻塞和唤醒指的是park()和unpark(); unpark()之后,在哪里park(),就从哪一行代码接着往下执行
3、笔记中说的第一个排队的节点指的是head节点的next;AQS队列中的一个节点head对应的thread永远是null
4、对于非公平锁,加锁失败,去排队之后,就不存在插队的情况;我们所说的加锁,其实就是尝试将state从0变成1;如果是重入锁,那就是在1的基础上再加1
5、AQS为什么要创建一个虚拟空节点?
因为在队列里面,每一个节点都要将前一个节点的waitStatus设置为-1,只有在前一个节点是-1的时候,在前一个节点释放锁的时候,会唤醒后面排队的线程;
那第一个节点没有前置节点,所以,就创建一个空节点,空节点可以理解为当前在执行的线程对应的node节点,在当前线程释放资源之后,会根据head的ws来判断是否需要唤醒下一个排队线程(也可以理解为第一个节点是当前执行线程的站队节点)

源码

我们通过对ReentrantLock的源码解析,来记录AQS的源码

ReentrantLock.lock()

ReentrantLock重入锁分为了公平锁和非公平锁,两者的区别是:在尝试加锁的时候,公平锁会判断当前线程是否可以加锁,如果可以加锁,就尝试cas,否则就去排队;非公平锁是,无论是否可以加锁,都强制cas加锁,加锁失败,就去排队

公平锁尝试加锁
/**
* 公平锁加锁流程:
*  1、先尝试加锁
*   1.1、如果加锁成功,就返回,流程结束
*   1.2、如果加锁失败,就判断是否可重入,可重入,就加锁成功,流程结束
*   1.3、如果未加锁成功,且不可重入,就去排队
*  2、排队addWaiter()
*   2.1、在排队的时候,先判断当前是否有在排队的节点,或者是否有空白的node节点;如果有,就直接将当前线程加入到队列中排队
*   2.2、如果没有在排队的节点、或者没有空白节点,就先new一个空白节点,插入到队头,然后将当前线程插入到队列,并设置为队尾
*  3、acquireQueued()方法:将排队节点的上一个节点的waitStatus设置为-1,然后进行park()
*
*
* 这里是尝试加锁,如果加锁失败,就放入到队列中
*
* 返回true,表示加锁成功,无需排队
* 如果tryAcquire返回false,表示需要去排队
*
*
* @param arg
*/
public final void acquire(int arg) {
   
    if (!tryAcquire(arg) &&
        acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
        selfInterrupt();
}

我们分开来看这几个方法

/**
* 这个方法的作用是:判断当前线程是否可以加锁,
*   如果未加锁,尝试加锁,加锁成功,就无需排队,加锁失败,就去排队
*   如果已经加过锁了,判断是否可重入,可重入,就state+1;不可重入就去排队
* 拿到当前是否已经加锁的标识:state(为0,表示未加锁;为1表示已经加锁)
* 1.如果未加锁:
*   1.1 尝试加锁,如果加锁成功,使用cas更新state的值
*     hasQueuedPredecessors();只有这个方法返回false,才表示当前线程可以加锁;否则,就会去排队
* 2.如果已经加锁
*   2.1 判断当前锁和已经加锁的是否是同一把锁,如果是同一把锁,可以重入&#x
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 5
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 5
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值