AQS加锁机制Synchronized相似点详解

目录
  • 正文
  • 1. Synchronized加锁流程
  • 2. AQS加锁原理
  • 3. 总结

正文

在并发多线程的情况下,为了保证数据安全性,一般我们会对数据进行加锁,通常使用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的资料请关注我们其它相关文章!

(0)

相关推荐

  • Java AQS中ReentrantReadWriteLock读写锁的使用

    目录 一. 简介 二. 接口及实现类 三.使用 四. 应用场景 五. 锁降级 六.源码解析 七.总结 一. 简介 为什么会使用读写锁? 日常大多数见到的对共享资源有读和写的操作,写操作并没有读操作那么频繁(读多写少),在没有写操作的时候,多个线程同时读一个资源没有任何问题,所以应该允许多个线程同时读取共享资源(读读可以并发):但是如果一个线程想去写这些共享资源,就不应该允许其他线程对该资源进行读和写操作了(读写,写读,写写互斥).在读多于写的情况下,读写锁能够提供比排它锁更好的并发性和吞吐量.

  • Java AQS信号量Semaphore的使用

    目录 一.什么是Semaphore 二.Semaphore的使用 三.Semaphore源码分析 一.什么是Semaphore Semaphore,俗称信号量,它是操作系统中PV操作的原语在java的实现,它也是基于AbstractQueuedSynchronizer实现的. Semaphore的功能非常强大,大小为1的信号量就类似于互斥锁,通过同时只能有一个线程获取信号量实现.大小为n(n>0)的信号量可以实现限流的功能,它可以实现只能有n个线程同时获取信号量. PV操作是操作系统一种实现进程

  • Java AQS中ReentrantLock条件锁的使用

    目录 一.什么是AQS 1.定义 2.特性 3.属性 4.资源共享方式 5.两种队列 6.队列节点状态 7.实现方法 二.等待队列 1.同步等待队列 2.条件等待队列 三.condition接口 四.ReentrantLock 五.源码解析 一.什么是AQS 1.定义 java.util.concurrent包中的大多数同步器实现都是围绕着共同的基础行为,比如等待队列.条件队列.独占获取.共享获取等,而这些行为的抽象就是基于AbstractQueuedSynchronizer(简称AQS)实现的

  • Java AQS中闭锁CountDownLatch的使用

    目录 一. 简介 二. 使用 三. 应用场景 四. 底层原理 五. CountDownLatch与Thread.join的区别 一. 简介 CountDownLatch(闭锁)是一个同步协助类,允许一个或多个线程等待,直到其他线程完成操作集. CountDownLatch使用给定的计数值(count)初始化.await方法会阻塞直到当前的计数值(count)由于countDown方法的调用达到0,count为0之后所有等待的线程都会被释放,并且随后对await方法的调用都会立即返回.这是一个一次

  • AQS核心流程解析cancelAcquire方法

    目录 引出问题 更新正常节点的链表 当前取消节点是tail节点的情况 当前取消节点是非tail节点的情况 引出问题 首先,先考虑一个问题,什么条件会触发cancelAcquire()方法? cancelAcquire()方法的反向查找 可以清楚的看到在互斥锁和共享锁的拿锁过程中都是有调用此方法的,而cancelAcquire()方法是写在finally代码块中,并且使用failed标志位来控制cancelAcquire()方法的执行.可以得出,在触发异常的情况下会执行cancelAcquire(

  • Java的锁机制:synchronized和CAS详解

    目录 一 为什么要用锁 二 synchronized怎么实现的 三 CAS来者何人 四synchronized和CAS孰优孰劣 轻量级锁 重量级锁 总结 提到Java的知识点一定会有多线程,JDK版本不断的更迭很多新的概念和方法也都响应提出,但是多线程和线程安全一直是一个重要的关注点.比如说我们一入门就学习的synchronized怎么个实现和原理,还有总是被提到的CAS是啥,他和synchronized关系是啥?这里大概会让你对这些东西有一个认识. 一 为什么要用锁 我们使用多线程肯定是为了提

  • Java中synchronized实现原理详解

    记得刚刚开始学习Java的时候,一遇到多线程情况就是synchronized,相对于当时的我们来说synchronized是这么的神奇而又强大,那个时候我们赋予它一个名字"同步",也成为了我们解决多线程情况的百试不爽的良药.但是,随着我们学习的进行我们知道synchronized是一个重量级锁,相对于Lock,它会显得那么笨重,以至于我们认为它不是那么的高效而慢慢摒弃它. 诚然,随着Javs SE 1.6对synchronized进行的各种优化后,synchronized并不会显得那么

  • Java线程之线程同步synchronized和volatile详解

    上篇通过一个简单的例子说明了线程安全与不安全,在例子中不安全的情况下输出的结果恰好是逐个递增的(其实是巧合,多运行几次,会产生不同的输出结果),为什么会产生这样的结果呢,因为建立的Count对象是线程共享的,一个线程改变了其成员变量num值,下一个线程正巧读到了修改后的num,所以会递增输出. 要说明线程同步问题首先要说明Java线程的两个特性,可见性和有序性.多个线程之间是不能直接传递数据交互的,它们之间的交互只能通过共享变量来实现.拿上篇博文中的例子来说明,在多个线程之间共享了Count类的

  • Java Synchronized的使用详解

    1.为什么要使用synchronized 在并发编程中存在线程安全问题,主要原因有:1.存在共享数据 2.多线程共同操作共享数据.关键字synchronized可以保证在同一时刻,只有一个线程可以执行某个方法或某个代码块,同时synchronized可以保证一个线程的变化可见(可见性),即可以代替volatile. 2.实现原理 synchronized可以保证方法或者代码块在运行时,同一时刻只有一个方法可以进入到临界区,同时它还可以保证共享变量的内存可见性 3.synchronized的三种应

  • java中注解机制及其原理的详解

    java中注解机制及其原理的详解 什么是注解 注解也叫元数据,例如我们常见的@Override和@Deprecated,注解是JDK1.5版本开始引入的一个特性,用于对代码进行说明,可以对包.类.接口.字段.方法参数.局部变量等进行注解.它主要的作用有以下四方面: 生成文档,通过代码里标识的元数据生成javadoc文档. 编译检查,通过代码里标识的元数据让编译器在编译期间进行检查验证. 编译时动态处理,编译时通过代码里标识的元数据动态处理,例如动态生成代码. 运行时动态处理,运行时通过代码里标识

  • python字符串驻留机制的使用范围知识点详解

    1.字符串的长度为0和1时. 2.符合标识符的字符串. 3.字符串只在编译时进行驻留,而非运行时. 4.[-5,256]之间的整数数字. 实例 >>> str1='jiumo' >>> str2='jiumo' >>> str1 is str2 True >>> id(str1) 1979078421896 >>> id(str2) 1979078421896 知识点扩充: 驻留时机 所有长度为 0 和长度为 1 的

  • Pytorch图像处理注意力机制解析及代码详解

    什么是注意力机制 注意力机制是一个非常有效的trick,注意力机制的实现方式有许多,我们一起来学习一下 注意力机制是深度学习常用的一个小技巧,它有多种多样的实现形式,尽管实现方式多样,但是每一种注意力机制的实现的核心都是类似的,就是注意力. 注意力机制的核心重点就是让网络关注到它更需要关注的地方. 当我们使用卷积神经网络去处理图片的时候,我们会更希望卷积神经网络去注意应该注意的地方,而不是什么都关注,我们不可能手动去调节需要注意的地方,这个时候,如何让卷积神经网络去自适应的注意重要的物体变得极为

  • Go语言开发框架反射机制及常见函数示例详解

    目录 基本介绍 反射中常见函数和概念 reflect.TypeOf(变量名) reflect.ValueOf(变量名) 变量.interface{}和reflect.Value是可以相互转换的 基本使用 反射注意事项 反射的最佳实践 基本介绍 反射可以在运行时动态获取变量的各种信息,比如变量的类型,类别 如果是结构体变量,还可以获取到结构体本身的信息 通过反射,可以修改变量的值,可以调用关联的方法 使用反射,需要import("reflect") 示意图 反射中常见函数和概念 refl

  • RocketMq 消息重试机制及死信队列详解

    目录 生产者消息重试 消费者消息重试 并发消费 顺序消费 并发消费和顺序消费区别 死信队列 实践出真知 公共部分创建 测试并发消费 并发消费状态 测试顺序消费 顺序消费状态 测试死信队列 死信队列特性 生产者消息重试 消息队列中的消息消费时并不能保证总是成功的,那失败的消息该怎么进行消息补偿呢?这就用到今天的主角消息重试和死信队列了. 有时因为网路等原因生产者也可能发送消息失败,也会进行消息重试,生产者消息重试比较简单,在springboot中只要在配置文件中配置一下就可以了. # 异步消息发送

  • Netty的Handler链调用机制及如何组织详解

    目录 什么是 Handler Handler 是怎么被组织起来的 Handler 链调用机制 简述 ChannelPipeline 如何调度 handler 什么是 Handler Netty是一款基于NIO的异步事件驱动网络应用框架,其核心概念之一就是Handler.而Handler是Netty中处理事件的核心组件,用于处理入站和出站的数据流,实现业务逻辑和网络协议的处理. 在Netty中,Handler是一个接口,主要分为两种:ChannelInboundHandler(入站Handler)

随机推荐