AQS加锁机制Synchronized相似点详解

 更新时间:2022年10月21日 11:38:51   作者:一灯架构  
这篇文章主要为大家介绍了AQS加锁机制Synchronized相似点详解,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步,早日升职加薪

正文

在并发多线程的情况下,为了保证数据安全性,一般我们会对数据进行加锁,通常使用Synchronized或者ReentrantLock同步锁。Synchronized是基于JVM实现,而ReentrantLock是基于Java代码层面实现的,底层是继承的AQS

AQS全称 AbstractQueuedSynchronizer ,即抽象队列同步器,是一种用来构建锁和同步器的框架。

我们常见的并发锁ReentrantLockCountDownLatchSemaphoreCyclicBarrier都是基于AQS实现的,所以说不懂AQS实现原理的,就不能说了解Java锁。

当我仔细研究AQS底层加锁原理,发现竟然跟Synchronized加锁原理有惊人的相似。让我突然想到一句名言,记不清怎么说了,意思是框架底层原理很相似,大家多学习底层原理。

Synchronized的加锁流程在前几篇文章已经详细讲过,没看过一块再温习一下。

1. Synchronized加锁流程

我们先想一下Synchronized的加锁需求,如果让你设计Synchronized对象锁存储结构,该怎么设计?

  • 多个线程执行到Synchronized代码块,只有一个线程获取锁,然后执行同步代码块(需要记录哪个线程获取了对象锁)。
  • 其他线程被阻塞(被阻塞的线程,是不是可以用链表设计个阻塞队列?)
  • 持有锁的线程调用wait方法,释放锁,等待被唤醒(等待的线程,是不是可以用链表设计个等待队列?)。
  • 被阻塞的线程开始竞争锁
  • 调用notify方法,唤醒等待的线程,被唤醒的线程进入阻塞队列,一块竞争锁。

上面描述了Synchronized的加锁流程,Synchronized对象锁存储结构是不是跟咱们想的一样?实际就是的。

下面是对象锁的存储数据结构(由C++实现):

ObjectMonitor() {
    _header       = NULL;
    _count        = 0;
    _waiters      = 0,
    _recursions   = 0;
    _object       = NULL;
    _owner        = NULL; // 持有锁的线程
    _WaitSet      = NULL; // 等待队列,存储处于wait状态的线程
    _WaitSetLock  = 0 ;
    _Responsible  = NULL ;
    _succ         = NULL ;
    _cxq          = NULL ;
    FreeNext      = NULL ;
    _EntryList    = NULL ; // 阻塞队列,存储处于等待锁block状态的线程
    _SpinFreq     = 0 ;
    _SpinClock    = 0 ;
    OwnerIsThread = 0 ;
  }

上图展示了对象锁的基本工作机制:

  • 当多个线程同时访问一段同步代码时,首先会进入 _EntryList队列中阻塞。
  • 当某个线程获取到对象的对象锁后进入临界区域,并把对象锁中的 _owner变量设置为当前线程,即获得对象锁。
  • 若持有对象锁的线程调用 wait() 方法,将释放当前持有的对象锁,_owner变量恢复为null,同时该线程进入 _WaitSet 集合中等待被唤醒。
  • 在_WaitSet集合中的线程被唤醒,会被再次放到_EntryList队列中,重新竞争获取锁。
  • 若当前线程执行完毕也将释放对象锁并复位变量的值,以便其他线程进入获取锁。

Synchronized对象锁存储结构和加锁流程,竟然跟咱们想的一样。

再看一下ReentrantLock的存储结构和加锁流程,有没有相似的地方。

2. AQS加锁原理

先分析一下,我们使用AQS的加锁需求:

  • 多个线程执行到ReentrantLock.lock方法的时候,只有一个线程获取锁,然后执行同步代码块(需要记录哪个线程获取了对象锁)。
  • 其他线程被阻塞(被阻塞的线程,是不是可以用链表设计个阻塞队列?名叫”同步队列“?)
  • 持有锁的线程调用await方法,释放锁,等待被唤醒(等待的线程,是不是可以用链表设计个等待队列?名叫”条件队列“?)。
  • 被阻塞的线程开始竞争锁
  • 调用signal方法,唤醒等待的线程,被唤醒的线程进入阻塞队列,一块竞争锁。

AQS的需求跟Synchronized一模一样。

我们再看一下AQS实际的加锁机制是怎么设计的?是不是跟Synchronized相似?

AQS的加锁流程并不复杂,只要理解了同步队列条件队列,以及它们之间的数据流转,就算彻底理解了AQS

  • 当多个线程竞争AQS锁时,如果有个线程获取到锁,就把ower线程设置为自己
  • 没有竞争到锁的线程,在同步队列中阻塞(同步队列采用双向连接,尾插法)。
  • 持有锁的线程调用await方法,释放锁,追加到条件队列的末尾(条件队列采用单链条,尾插法)。
  • 持有锁的线程调用signal方法,唤醒条件队列的头节点,并转移到同步队列的末尾。
  • 同步队列的头节点优先获取到锁

可以看到AQSSynchronized的加锁流程几乎是一模一样的,AQS中同步队列就是SynchronizedEntryListAQS中条件队列就是Synchronized中的waitSet,两个队列之间的数据转移流程也是一样的。

3. 总结

AQSSynchronized的加锁流程是一样的,都是通过同步队列和条件队列实现的,阻塞状态的线程被放到同步队列中,等待状态的线程被放到条件队列中,从条件队列唤醒的线程又被转移到同步队列末尾,一块竞争锁。

看完AQS加锁流程,还没有人不懂AQS的?

下篇文章再讲一下AQS加锁具体的源码实现。里面有很多精巧的设计,值得我们学习。

比如:

为什么同步队列要设计成双向链表?而条件队列要设计成单链表?

为什么AQS加锁性能这么好(乐观锁CAS使用)?

同步队列和条件队列中节点怎么用一个对象实现?

释放锁后,怎么唤醒同步队列中线程?

以上就是AQS加锁机制Synchronized相似点详解的详细内容,更多关于AQS加锁机制Synchronized的资料请关注脚本之家其它相关文章!

相关文章

  • Java中自定义LRU缓存详解

    Java中自定义LRU缓存详解

    这篇文章主要介绍了Java中自定义LRU缓存详解,基于LRU算法的缓存系统,可以在达到缓存容量上限时,清理最近最少使用的数据,为新的数据的插入腾出空间,需要的朋友可以参考下
    2023-09-09
  • 通过MyBatis读取数据库数据并提供rest接口访问

    通过MyBatis读取数据库数据并提供rest接口访问

    这篇文章主要介绍了通过MyBatis读取数据库数据并提供rest接口访问 的相关资料,非常不错,具有参考借鉴价值,需要的朋友可以参考下
    2016-08-08
  • HttpServletRequest对象简介_动力节点Java学院整理

    HttpServletRequest对象简介_动力节点Java学院整理

    这篇文章主要为大家详细介绍了HttpServletRequest对象简介的相关资料,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2017-07-07
  • SpringBoot+ShardingSphereJDBC实现读写分离详情

    SpringBoot+ShardingSphereJDBC实现读写分离详情

    这篇文章主要介绍了SpringBoot+ShardingSphereJDBC实现读写分离详情,通过用​​MySQL​​进行一主一从的主从复制展开全文内容,需要的朋友可以参考一下
    2022-08-08
  • Java Fluent Mybatis实战之构建项目与代码生成篇上

    Java Fluent Mybatis实战之构建项目与代码生成篇上

    Java中常用的ORM框架主要是mybatis, hibernate, JPA等框架。国内又以Mybatis用的多,基于mybatis上的增强框架,又有mybatis plus和TK mybatis等。今天我们介绍一个新的mybatis增强框架 fluent mybatis
    2021-10-10
  • 详解扩展tk.mybatis的批量更新的功能

    详解扩展tk.mybatis的批量更新的功能

    tk.mybatis没有带批量更新的功能,批量更新却是经常使用的,所以本文介绍了一下tk.mybatis的批量更新功能,感兴趣的可以了解一下
    2021-12-12
  • java中封装JDBC工具类的实例分析

    java中封装JDBC工具类的实例分析

    在本篇内容里小编给各位分享了一篇关于java中封装JDBC工具类的实例分析,对此有兴趣的朋友们可以学习下。
    2021-03-03
  • 深入浅出讲解Spring框架中依赖注入与控制反转及应用

    深入浅出讲解Spring框架中依赖注入与控制反转及应用

    依赖注入(Dependency Injection)和控制反转(Inversion of Control)是同一个概念。具体含义是:当某个角色(可能是一个Java实例,调用者)需要另一个角色(另一个Java实例,被调用者)的协助时,在 传统的程序设计过程中,通常由调用者来创建被调用者的实例
    2022-03-03
  • 教你用Springboot实现拦截器获取header内容

    教你用Springboot实现拦截器获取header内容

    项目中遇到一个需求,对接上游系统是涉及到需要增加请求头,请求头的信息是动态获取的,需要动态从下游拿到之后转给上游,文中非常详细的介绍了该需求的实现,需要的朋友可以参考下
    2021-05-05
  • SpringBoot项目使用jasypt加解密的方法

    SpringBoot项目使用jasypt加解密的方法

    jasypt是一个通用的加解密库,我们可以使用它在配置文件中对数据库密码进行加密,以确保其安全性,接下来通过本文给大家介绍SpringBoot项目使用jasypt加解密的方法,感兴趣的朋友一起看看吧
    2022-05-05

最新评论