JUC学习:AQS介绍及源码分析

目录

1 AQS介绍

2 AQS源码分析

2.1 ReentrantLock源码分析解释AQS思想

3 总结


1 AQS介绍

        AQS是AbstractQueuedSynchronizer的简称。AQS提供了一种实现阻塞锁和一系列依赖FIFO等待队列的同步器的框架,如下图所示。

        简单来说,就是一个state变量,与一个等待队列(双向链表)。当多个线程争夺锁时,只有一个线程能的到锁,得到后修改state变量的值,表示锁处于占用状态,其他未挣得到锁的线程则进入等待队列。当state的状态变为锁空闲时,队列中的等待线程再继续争夺锁。
在这里插入图片描述
该图片取自:https://blog.csdn.net/qq_43141726/article/details/121562620

2 AQS源码分析

2.1 ReentrantLock源码分析解释AQS思想

        ReentrantLock拥有一个可传递boolean类型参数的构造方法,当该参数为true时,代表ReentrantLock使用公平锁,反之则使用非公平锁。注:使用ReentrantLock的空参构造时默认使用非公平锁。

  • 公平锁:在多线程下,每个线程按照先来后到的顺序执行。
  • 非公平锁:在多线程下,哪个线程先抢到锁,哪个线程先执行。

2.1.1 使用非公平锁模式讲解aqs源码

        场景设定:线程A首先抢夺到锁,并进行一个耗时操作,线程B获取不到锁被阻塞。

        非公平锁NonfairSync类继承了Sync类,Sync类又继承了AbstractQueuedSynchronizer(AQS)抽象类。

        线程A调用lock()方法会先进行一个CAS抢夺锁的操作,该操作会将AQS的状态位由0改为1(表示目前锁已被占用,处于非空闲状态),并将当前线程A设置为拥有锁的线程,如下图所示。

        此时线程B加入进来并调用lock()方法,则在进行CAS修改AQS状态时,发现状态已经被线程A设置为了1(即锁处于占用状态),则进入else中执行acpuire()方法。

         acpuire()方法源码如下。此时线程B调用该方法,且参数arg为1。在方法体中先调用了tryAcquire()方法,该方法在AQS中有公平锁与非公平锁两中实现,此处看非公平锁的实现。

         tryAcquire()方法在非公平锁的实现中是nonfairTryAcquire()方法,如下图所示。线程B进入该方法后会先判断AQS的状态,此时该状态已被线程A改为1(表示锁处于占用状态),再判断获得锁的线程是不是当前线程,线程A目前持有锁,所以该条件也不通过。该方法直接放回false。

         回到acpuire()方法源码中,tryAcquire()方法返回了false,但是!取反,则为true。则继续执行后续的条件判断。第二个条件是acquireQueued(addWaiter(Node.EXCLUSIVE), arg)),该条件包含了两个方法,由内到外,先看addWaiter(Node.EXCLUSIVE)方法。

        addWaiter()方法中的参数 Node.EXCLUSIVE 表示该节点为独占模式,另外还有 Node.SHARED 表示该节点为共享模式。在该方法中,会将线程B封装到一个Node节点中,并判断AQS链表最后一个节点是否为null,由于第一次使用,所以为null。则直接进入enq方法。

        线程B进入enq方法后,该方法内会初始化一个空的虚拟节点作为AQS链表的头节点,并将封装线程B的节点排列在虚拟结点之后(此后若有其它线程加入,也按照这个规则依次排列)。这就是AQS中等待线程入队列的方法。此时enq()方法执行完毕,并返回封装线程B的节点,即上图enq()外层的addWaiter()方法执行完毕,返回enq()方法返回的线程B节点。

         addWaiter()方法执行完之后,进入acquireQueued(addWaiter(Node.EXCLUSIVE), arg))外层的acquireQueued()方法中,第一个参数是addWaiter()方法返回的线程B节点,第二个参数arg是int类型的1。该方法中会先判断当前节点的前一个节点是否为头节点,由enq方法流程可知线程B的前一个节点是头节点(虚拟节点),则线程B会再次调用tryAcquire方法尝试抢占锁,成功则修改各种状态,失败则在parkAndCheckInterrupt()方法中会调用LockSupport的park()方法使线程B阻塞。

         以上就是在ReentrantLock下使用非公平锁的lock方法源码分析,其中也包含了AQS的底层结构。下面进行unlock()方法源码的解读,上述可知lock()方法会将得不到锁的线程加进等待队列中,unlock()则是使线程重新进入就绪状态。

        unlock()方法调用的是,Sync类的release()方法。

        在 release() 方法中,主要进行了两个操作。首先修改AQS状态表示,由0改为1,代表线程B可以继续争夺锁了,然后将AQS队列中的队头的下一个节点内的线程由阻塞状态变为就绪状态(此时头节点为虚拟节点,头节点的下一个节点是线程B节点)。

         下面详细分析tryRelease()方法与unparkSuccessor()方法。在tryRelease()方法中,会将持有锁的线程属性置为null(该属性值原本是线程A,此时置为null),并将AQS状态置为0,表示当前锁处于空闲状态。

         在unparkSuccessor()方法中,主要操作就是对AQS等待队列的头节点的下一个节点调用unpark()方法,即对线程B调用了unpark()方法,线程B重新开始运作并重新抢夺锁。

         在对线程B进行unpark()后,回到lock()方法中的acquireQueued()方法,这是一个for循环的方法。线程B在被加入等待队列后就是在这个方法中被调用park()方法阻塞了。此时线程A执行完毕调用了unlock()方法,导致对线程B调用了unpark()方法,因此线程B会继续走for循环,如下图。在循环中,线程B会继续抢夺锁。

         以上就是lock()与unlock()方法整个流程的源码分析。

3 总结

        AQS就是一个实现了在多线程情况下,控制线程的阻塞与重新争夺锁的行为的一个同步队列。主要包含一个state变量与一个等待队列,得不到锁的线程入队列,得到锁的线程出队列。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值