AQS概述+基于AQS的Reentrantlock源码解析

AQS概述

AQS总览:

AQS(AbstractQueuedSynchronizer)是 Java 中用于构建同步器(synchronizer)的抽象框架,它提供了底层的同步机制,使得开发者可以基于它构建自定义的锁、同步工具以及并发数据结构。AQS 是 Java 并发包(java.util.concurrent)中重要的组成部分,它的核心思想是使用一个内部的等待队列来管理线程的竞争和等待状态,以替代传统的忙等(spin)方式,从而提高并发性能。同步器的设计是基于模板⽅法模式的。

在这里插入图片描述

由类的定义可以看出,AQS是一个抽象类。其内部包含两个子类。其中Node子类是存储AQS等待队列的结点。
在这里插入图片描述

但是其内部方法没有一个是抽象方法。原因是设计者如果把方法都定义为抽象的,那么其实现类都要去实现这些方法,而里面的诸多方法不一定都需要实现,我们可以根据实现类的功能按需实现。比如Reentrantlock是独占式的,它只需要按需实现AQS类里面声明的独占方法,而不需要实现共享方法。

相关概念:

同步器的角色:AQS 可以看作是一种同步器的框架,它定义了同步器的基本行为和模板,但不提供具体的同步策略。开发者可以继承 AQS,根据自己的需求实现同步器,例如独占锁、共享锁、信号量等。

状态管理:AQS 使用一个整数状态变量(state)来表示同步状态。不同的同步器可以使用这个状态变量来表示不同的含义。例如,在独占锁中,state 可以表示锁的占用状态;在信号量中,state 可以表示可用的许可数量。状态信息通过 protected 类型的 getState,setState,compareAndSetState 进⾏操作。

  • 状态变量:

在这里插入图片描述

  • 状态信息相关方法:

在这里插入图片描述

等待队列:AQS 使用一个等待队列(Wait Queue)来管理等待获取同步资源的线程。等待队列通常是一个双向链表,线程会加入队列尾部等待,当资源可用时,会从队列头部唤醒等待线程。

acquire 和 release 方法:AQS 提供了 acquire 和 release 等方法来实现获取和释放同步资源的操作。不同的同步器可以根据自己的需求重写这些方法,来实现自定义的同步策略。

Condition 条件:AQS 支持 Condition 接口,用于支持条件等待和通知操作。Condition 可以通过 AQS.newCondition() 方法创建,允许线程在某个条件满足时等待,或者通知其他等待的线程。

共享模式和独占模式:AQS 支持两种同步模式,即共享模式和独占模式。共享模式用于多个线程可以同时获取资源,例如信号量。独占模式用于只允许一个线程获取资源,例如独占锁。

回退机制:当一个线程无法获取同步资源时,它会被加入等待队列中,而不是忙等。这种等待机制降低了 CPU 的使用率,避免了浪费计算资源。

AQS对资源的共享方式

AQS(AbstractQueuedSynchronizer)是 Java 中用于构建同步器的抽象框架,它可以支持不同的同步方式,包括资源的独占模式和共享模式。下面分别介绍 AQS 对资源的这两种共享方式:
独占模式:
独占模式是 AQS 最常见的同步方式,用于实现独占锁(如 ReentrantLock)。
在独占模式下,只有一个线程可以同时获得锁,其他线程必须等待锁的释放。
AQS 为独占模式提供了 acquire 和 release 方法,用于获取和释放锁。这些方法可以被不同的线程调用。
当一个线程获得了独占锁,其他线程无法同时获得锁,它们会被放入等待队列,等待锁的释放。
共享模式:
共享模式用于实现资源的多线程并发访问,允许多个线程同时访问共享资源,而不会造成冲突。
在共享模式下,AQS 提供了 acquireShared 和 releaseShared 方法,用于获取和释放资源的访问权限。这些方法可以被不同的线程调用。
当多个线程同时获取资源的访问权限时,它们可以共享资源,而不会被阻塞。但当资源不可用时,线程会被放入等待队列,等待资源的释放。

总结

总之,AQS 是 Java 并发编程中的一个重要组件,它提供了底层的同步机制,允许开发者构建自定义的同步器,以满足各种并发控制需求。通过合理使用 AQS,可以实现高性能、高并发的多线程程序。一些常见的基于 AQS 的同步器包括 ReentrantLock、Semaphore、CountDownLatch 等。

Reentrantlock源码解析

使用AQS实现自定义的锁一般有以下步骤:

  1. 使⽤者继承 AbstractQueuedSynchronizer 并重写指定的⽅法。(这些重写⽅法很简单,⽆⾮
    是对于共享资源 state 的获取和释放)
  2. 将 AQS 组合在⾃定义同步组件的实现中,并调⽤其模板⽅法,⽽这些模板⽅法会调⽤使⽤
    者重写的⽅法。

Reentrantlock是基于AQS框架设计的一个独占模式的可重入锁,支持条件等待, 可以选择是公平锁还是非公平锁。ReentrantLock 的灵活性和功能使其成为 Java 并发编程中的重要工具,特别是在需要更多控制和功能的情况下。
接下来介绍一下其加锁方法lock();

lock()

第一步:当我们调用lock方法时,进入lock方法的实体。

首先会通过CAS的方式修改state的值,如果预期值和实际的值都为0,将state改为1,获得锁成功;然后将当前获得锁的线程设置为当前线程;如果CAS失败,没有获得锁,则调用acquire(1),进入第二步;

在这里插入图片描述
第二步:第一步获取锁失败的情况下,进入acquire(1)方法体,

在这里插入图片描述
首先还是会尝试通过tryAcquire(1)(这里以非公平为例)这个方式去尝试获得锁,其会调用nofairTryAcquire(1),这个方式还是以CAS的方式尝试获得锁,如果成功,就将当前获得锁的线程设置为当前线程,返回true代表获得锁成功。否则,就代表锁已经被其他线程获得,就将获得锁的线程的state+1,代表重入次数+1,(这里就体现了可重入性)并返回false代表获得锁失败。在这里插入图片描述
第三步:当acquire(1)方法中if语句第一个条件判断返回true,就代表成功获得了锁,由于其做了非逻辑语句,if中的语句不会执行,acquire方法结束;否则,进入第四步。

第四步:首先进入addWaiter(Node.EXCLUSIVE)这个方法体,这个方法的作用是构造等待队列,并将获得锁失败的线程放入等待队列。
在这里插入图片描述
在这里会将没有获得锁的线程封装到AQS的子类Node中,代表等待队列的一个节点,这里小插曲介绍一下Node类的结构:
在这里插入图片描述

这个类中显式定义了四个Int类型的参数,还有一个默认的0:
INITIAL=0    默认初始值
CANCELLED=1
SIGNAL    = -1
CONDITION = -2
PROPAGATE = -3
Reentrantlock中的lock方法值涉及到SIGNAL=-1,代表当前节点有责任或义务去唤醒下一个节点;INITIAL=0 代表当前节点没有任何责任或义务,通常为尾结点。
  • 首先创建一个Node空节点指向等待队列的尾结点,如果尾结点为空,代表当前队列为空,就将这个Node空节点插入队列。然后执行enq(node)方法,将当前包含当前等待线程的Node节点插在Node空节点后面。
  • 如果为尾结点不为空,就代表当前等待队列不为空,就直接执行enq(node)方法,将该节点入队。
    在这里插入图片描述
    第五步:执行acquireQueued(node, arg))方法,该方法主要作用是循环当前节点的前驱节点是否为空节点,如果是,就再次通过CAS的方式获取锁,如果获取锁成功,就继续运行,否则,执行shouldParkAfterFailedAcquire()方法,目的是将前驱节点的waitstatus设置为-1,表示前驱节点有义务唤醒当前节点,如果设置失败,就继续循环执行acquireQueued(node, arg))方法,否则执行parkAndCheckInterrupt()方法,将线程阻塞。
    在这里插入图片描述
    在这里插入图片描述

在这里插入图片描述

第六步:进入阻塞后的线程如果被唤醒或调用了unlock方法,就继续执行acquireQueued(node, arg))方法,去竞争锁。

总结

最后,以一张图片(此图来源:B站UP主MorningLightCode)来总结lock方法的流程:
在这里插入图片描述
本人是萌新程序员一枚,如果有讲得不对的地方,欢迎大家纠正!

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值