一文带你看懂Java中的Lock锁底层AQS到底是如何实现的

先获取锁的状态,判断锁的状态是不是等于0,等于0说明没人加锁,可以尝试去加,如果被加锁了,就会走else if,else if会判断加锁的线程是不是当前线程,是的话就给state 加 1,代表当前线程加了2次锁,就是可重入锁的意思(所谓的可重入就是代表一个线程可以多次获取到锁,只是将state 设置为多次,当线程多次释放锁之后,将state 设置为0才代表当前线程完全释放了锁)。

这里所有的条件假设都不成立。也就是线程2尝试加锁的时候,线程1并没有释放锁,那么这个方法就会返回false。

接下来就会走到addWaiter方法,这个方法很重要,就是将当前线程封装成一个Node,然后将这个Node放入双向链表中。addWaiter先根据指定模式创建指定的node节点,因为ReentrantLock是独占模式,所以传进去的EXCLUSIVE,这里通过当前线程和模式传入,初始化一个双向node节点,获取最后一个节点,根据最后一个节点是否存在来操作当前节点的父级。如果尾节点不存在会去调用enq去初始化

放入链表中之后如图。

然后调用acquireQueued方法

这个方法一进来也会尝试将当前节点去加锁,然后如果加锁成功就将当前节点设置为头节点,最后将当前线程中断,等待唤醒。

线程2进来的时候,刚好线程2的前一个节点是头节点,但是不巧的是调用tryAcquire方法,还是失败,那么此时就会走shouldParkAfterFailedAcquire方法,这个方法是在线程休眠之前调用的,很重要,我们来看看干了什么事。

判断当前节点的父级节点的状态,如果父级状态是-1,则代表当前线程可以被唤醒了。如果父级的状态为取消状态(什么叫非取消状态,就是tryLock方法等待了一些时间没获取到锁的线程就处于取消状态)就跳过父级,寻找下一个可以被唤醒的父级,然后绑定上节点关系,最后将父级的状态更改为-1。也就说,线程(Node)加入队列之后,如果没有获取到锁,在睡眠之前,会将当前节点的前一个节点设置为非取消状态的节点,然后将前一个节点的waitStatus设置为-1,代表前一个节点在释放锁的时候需要唤醒下一个节点。这一步骤主要是防止当前休眠的线程无法被唤醒。这一切设置成功之后,就会返回true。

接下来就会调用parkAndCheckInterrupt

,这个方法内部调用LockSupport.park方法,此时当前线程就会休眠。

到这一步线程2由于没有获取到锁,就会在这里休眠等待被唤醒。

来总结一下加锁的过程。

线程1先过来,发现没人加锁,那么此时就会加上锁。此时线程2过来,在线程2加锁的过程中,线程1始终没有释放锁,那么线程2就不会加锁成功(如果在线程2加锁的过程中线程1始终释放锁,那么线程2就会加锁成功),线程2没有加锁成功,就会将自己当前线程加入等待队列中(如果没有队列就先初始化一个),然后设置前一个节点的状态,最后通过LockSupport.park方法,将自己这个线程休眠。

如果后面还有线程3,线程4等等诸多的先过来,那么这些线程都会按照前面线程2的步骤,将自己插入链表后面再休眠。

释放锁

ok,说完加锁的过程之后,我们来看看释放锁干了什么。

ReentrantLock的unlock其实是调用AQS的release方法,我们直接进入release方法,看看是如何实现的

进入tryRelease方法,看一下Sync的实现

其实很简单,就是判断锁的状态,也就是加了几次锁,然后减去释放的,最后判断释放之后,锁的状态是不是0(因为可能线程加了多次锁,所以得判断一下),是的话说明当前这个锁已经释放完了,然后将占有锁的线程设置为null,然后返回true,

然后就会走接下来的代码。

就是判断当前链表头节点是不是需要唤醒队列中的线程。如果有链表的话,头结点的waitStatus肯定不是0,因为线程休眠之前,会将前一个节点的状态设置为-1,上面加锁的过程中有提到过。

接下来就会走unparkSuccessor方法,successor代表继承者的意思,见名知意,这个方法其实就会唤醒当前线程中离头节点最近的没有状态为非取消的线程。然后调用LockSupport.unpark,唤醒等待的线

然后线程就会从阻塞的那里苏醒过来,继续尝试获取锁。

我再次贴出这段代码。

获取到锁之后,就将头节点设置成自己。

对应我们的例子,就是线程1释放锁之后,就会唤醒在队列中线程2,先成2获取到锁之后,就会将自己前一个节点(也就是头节点)从链表中移除,将自己设置成头节点。该方法就会跳出死循环。

到这里,释放锁的过程就讲完了,其实很简单,就是当线程完完全全释放了锁,会唤醒当前链表中的没有取消的,离头结点最近的节点(一般就是链表中的第二个节点),然后被唤醒的节点就会获取到锁,将头节点设置为自己。

相信看完这篇文章,大家对AQS的底层有了更深层次的了解。AQS其实就是内部维护一个锁的状态变量state和一个双向链表,加锁成功就将state的值加1,加锁失败就将自己当前线程放入链表的尾部,然后休眠,等待其他线程完完全全释放锁之后将自己唤醒,唤醒之后会尝试加锁,加锁成功就会执行业务代码了。

到这里本文就结束了,如果你有什么疑问欢迎私信告诉我。

如果觉得这篇文章对你有所帮助,还请帮忙点赞、关注、转发一下,码字不易,非常感谢!

如果你想联系我,欢迎关注我的个人微信公众号 三友的java日记,每天都会发布技术性的文章,期待与你一起进步。

Original: https://www.cnblogs.com/zzyang/p/16317237.html
Author: 三友的java日记
Title: 一文带你看懂Java中的Lock锁底层AQS到底是如何实现的

原创文章受到原创版权保护。转载请注明出处:https://www.johngo689.com/622711/

转载文章受原作者版权保护。转载请注明原作者出处!

(0)

大家都在看

  • MySQL十四:单表最大2000W行数据

    转载~ 在互联网技术圈中有一个说法: 「MySQL 单表数据量大于 2000 W行,性能会明显下降」。网传这个说法最早由百度传出,真假不得而知。但是却成为了行业内一个默认的标准。 …

    Java 2023年6月8日
    0101
  • redis 基于SpringBoot Reids 的工具类

    redis 基于SpringBoot Reids 的工具类 package com.mhy.springredis.utils; import org.springframewor…

    Java 2023年6月16日
    076
  • 引迈信息惊喜周年庆 多重优惠劲爆来袭

    posted @2022-09-30 10:59 迈阿蜜 阅读(10 ) 评论() 编辑 Original: https://www.cnblogs.com/jnpf001/p/1…

    Java 2023年6月5日
    060
  • linux安装elasticsearch

    Elasticsearch是一个基于Apache Lucene(TM)的开源搜索引擎。无论在开源还是专有领域,Lucene可以被认为是迄今为止最先进、性能最好的、功能最全的搜索引擎…

    Java 2023年6月8日
    0106
  • 这类注解都不知道,还好意思说会Spring Boot ?

    不知道大家在使用Spring Boot开发的日常中有没有用过 @Conditionalxxx注解,比如 @ConditionalOnMissingBean。相信看过Spring B…

    Java 2023年6月14日
    086
  • Java中的String字符串及其常用方法

    字符串(String) 文章目录 字符串(String) 直接定义字符串 常用方法 字符串长度 toLowerCase() & toUpperCase()方法 trim()…

    Java 2023年6月9日
    096
  • Spring框架中使用了哪些设计模式及应用场景

    1、工厂模式 2、模板模式 3、代理模式 4、策略模式 5、单例模式 6、观察者模式 7、适配器模式 8、装饰者模式 Original: https://www.cnblogs.c…

    Java 2023年6月14日
    064
  • 常见电子书获取资源途经

    比起以往,22年后对于电子书的获取渠道有了更多了解,在此记录。 先来点以前获取电子书的途经。 直接百度,能搜索得到多数电子资源,质量通常难以保证,精力、时间成本高。 各种微信公众号…

    Java 2023年6月7日
    079
  • 原来你是这样的JAVA[05]–String

    1.从概念上讲,java字符串就是Unicode字符串。2.字符串拼接用指定分隔符拼接字符串数组时,使用StringJoiner或者String.join()更方便;用String…

    Java 2023年5月29日
    0103
  • Java地址:

    GitHub:https://github.com/nanchen2251 个人博客:https://nanchen2251.github.io/ 简书地址:http://www….

    Java 2023年5月29日
    065
  • rose-jade自动生成dao、model

    博客园 :当前访问的博文已被密码保护 请输入阅读密码: Original: https://www.cnblogs.com/FCWORLD/p/5371923.htmlAuthor…

    Java 2023年6月6日
    054
  • Callable接口

    Callable接口 特点 1.有返回 2.可以抛出异常 代码实现,Callable接口开启线程 public class CallableTest { public static…

    Java 2023年6月5日
    078
  • Spring Data JPA系列5:让IDEA自动帮你写JPA实体定义代码

    大家好,又见面了。 这是本系列的最后一篇文档啦,先来回顾下前面4篇: 在第1篇《Spring Data JPA系列1:JDBC、ORM、JPA、Spring Data JPA,傻傻…

    Java 2023年6月7日
    066
  • Docker系列-Docker Hub

    Docker Hub 目前 Docker 官方维护了一个公共仓库 Docker Hub,大部分需求都可以通过在 Docker Hub 中直接下载镜像来实现。 可以通过执行 dock…

    Java 2023年6月8日
    065
  • Spring源码之AOP的使用

    Spring往期精彩文章 Spring源码搭建 Spring源码阅读一 前言 我们都知道Java是一门面向对象(OOP)的语言,所谓万物皆对象。但是它也存在着一些个弊端:当你需要给…

    Java 2023年6月7日
    081
  • 《Spring实战》学习笔记-第五章:构建Spring web应用

    之前一直在看《Spring实战》第三版,看到第五章时发现很多东西已经过时被废弃了,于是现在开始读《Spring实战》第四版了,章节安排与之前不同了,里面应用的应该是最新的技术。 本…

    Java 2023年5月29日
    075
亲爱的 Coder【最近整理,可免费获取】👉 最新必读书单  | 👏 面试题下载  | 🌎 免费的AI知识星球