首页 > 其他分享 >synchronized原理

synchronized原理

时间:2024-06-17 18:33:44浏览次数:13  
标签:Word synchronized CAS Mark 对象 线程 原理 轻量级

锁的四种状态

  • 001无锁

  • 101偏向锁。

  • 00轻量级锁。此状态下,线程可能通过自旋的方式竞争锁。

  • 10重量级锁。线程阻塞被挂起,需要从用户态切换到内核态进行系统调用,获取CPU调度权,对性能有较大影响。

    轻量级锁,重量级锁是2Bit

  • 锁等级顺序 : 001无锁 --》101偏向锁 --》00轻量级锁 --》10重量级锁

    img

自旋锁

自旋锁
  • 概念:一种获取锁的机制。通过自旋的方式获取锁。
  • 场景:适用于锁保护的临界区很小的情况,临界区越小,锁占用的时间就很短。
  • 优点:非阻塞或睡眠的获取锁,无需从用户态转为内核态, 避免线程切换带来的开销
  • 缺点:占用了CPU时间片,临界区很大的时候,白白消耗CPU资源,性能浪费
  • JVM参数:
    • -XX:+UseSpinning :开启/关闭自旋。默认关闭。
    • -XX:PreBlockSpin:自旋最大次数。默认10
适应性自旋锁
  • 背景:自旋锁有时会产生无效的自旋,最终结果失败,白白损耗资源。

  • 概念:自选锁的升级版。基于自旋结果自动调节自旋次数,为了节省资源。

  • 自旋锁超过最大次数没有成功则被挂起。如果一个自旋锁在不超过最大次数自旋成功,那么自旋的最大次数就会更多。反之如果很少自旋成功,那就会减少自旋次数或者放弃自旋,直接进入挂起。

偏向锁

  • 背景:大多数情况下,锁不仅不存在多线程竞争,而且总是由同一线程多次获得。为了在没有多线程竞争的情况下尽量减少不必要的轻量级锁执行路径。因为轻量级锁的加锁解锁操作是需要依赖多次CAS原子指令的,而偏向锁只需要在置换ThreadID的时候依赖一次CAS原子指令(由于一旦出现多线程竞争的情况就必须撤销偏向锁,所以偏向锁的撤销操作的性能损耗也必须小于节省下来的CAS原子指令的性能消耗),在只有一个线程执行同步块时进一步提高性能。偏向锁就是为了消除CAS,降低Cache一致性流量

  • 概念:单线程执行代码块时的锁机制。如果多个线程竞争, 则会升级为轻量级锁或重量级锁。

  • 场景:单线程下加锁或重复加锁。

  • 原理:

    • 加锁 :锁对象头和栈帧中的锁记录([Lock Record](#Lock Record))里存储锁偏向的线程ID。以后该线程进入和退出同步块时不需要花费CAS操作来争夺锁资源,只需要检查是否为偏向锁、锁标识为以及ThreadID即可。

      1. 检测Mark Word是否为可偏向状态,即是否为偏向锁1,锁标识位为01;
      2. 若为可偏向状态,则测试线程ID是否为当前线程ID,如果是,则执行步骤(5),否则执行步骤(3);
      3. 如果测试线程ID不为当前线程ID,则通过CAS操作竞争锁,竞争成功,则将Mark Word的线程ID替换为当前线程ID,否则执行线程(4);
      4. 通过CAS竞争锁失败,证明当前存在多线程竞争情况,当到达全局安全点,获得偏向锁的线程被挂起,偏向锁升级为轻量级锁,然后被阻塞在安全点的线程继续往下执行同步代码块;
      5. 执行同步代码块;
    • 解锁:偏向锁的释放采用了 一种只有竞争才会释放锁的机制,线程是不会主动去释放偏向锁,需要等待其他线程来竞争。偏向锁的撤销需要 等待全局安全点(这个时间点是上没有正在执行的代码)。

      1. 暂停拥有偏向锁的线程;
      2. 判断锁对象是否还处于被锁定状态,否,则恢复到无锁状态(01),以允许其余线程竞争。是,则挂起持有锁的当前线程, 并将指向当前线程的锁记录([Lock Record](#Lock Record))地址的指针放入对象头Mark Word,升级为轻量级锁状态(00),然后恢复持有锁的当前线程,进入轻量级锁的竞争模式;
  • JVM参数:

    • -XX:-UseBiasedLocking。开启/关闭偏向锁。JDK6默认开启。

轻量级锁

  • 背景:在没有多线程竞争的前提下,减少传统的重量级锁使用操作系统互斥量产生的性能消耗。

  • 概念:通过CAS与自旋获取锁

  • 场景:线程交替执行同步块的情况。如果存在同一时间访问同一锁的情况,必然就会导致轻量级锁膨胀为重量级锁。

  • 原理:

    • 加锁:

      1. 在线程进入同步块时,如果同步对象锁状态为无锁状态(锁标志位为“01”状态,是否为偏向锁为“0”),虚拟机首先将在当前线程的栈帧中建立一个名为锁记录(Lock Record)的空间,用于存储锁对象目前的Mark Word的拷贝,官方称之为 Displaced Mark Word。此时线程堆栈与对象头的状态如下图所示:
      2. 拷贝对象头中的Mark Word复制到锁记录(Lock Record)中;
      3. 拷贝成功后,虚拟机将使用CAS操作尝试将对象Mark Word中的Lock Word更新为指向当前线程Lock Record的指针,并将Lock record里的owner指针指向object mark word。如果更新成功,则执行步骤(4),否则执行步骤(5);
      4. 如果这个更新动作成功了,那么当前线程就拥有了该对象的锁,并且对象Mark Word的锁标志位设置为“00”,即表示此对象处于轻量级锁定状态,此时线程堆栈与对象头的状态如下图所示:
      5. 如果这个更新操作失败了,虚拟机首先会检查对象Mark Word中的Lock Word是否指向当前线程的栈帧,如果是,就说明当前线程已经拥有了这个对象的锁,那就可以直接进入同步块继续执行。否则说明多个线程竞争锁,进入自旋执行(3),若自旋结束时仍未获得锁,轻量级锁就要膨胀为重量级锁,锁标志的状态值变为“10”,Mark Word中存储的就是指向重量级锁(互斥量)的指针,当前线程以及后面等待锁的线程也要进入阻塞状态。

      img

      img

    • 解锁:

      1. 通过CAS操作尝试把线程中复制的Displaced Mark Word对象替换当前的Mark Word;
      2. 如果替换成功,整个同步过程就完成了,恢复到无锁状态(01);
      3. 如果替换失败,说明有其他线程尝试过获取该锁(此时锁已膨胀),那就要在释放锁的同时 ,唤醒被挂起的线程;

img

  • 问题:

    • 为什么升级为轻量锁时要把对象头里的Mark Word复制到线程栈的锁记录中呢?

      因为在申请对象锁时 需要以该值作为CAS的比较条件,同时在升级到重量级锁的时候,能通过这个比较判定是否在持有锁的过程中此锁被其他线程申请过,如果被其他线程申请了,则在释放锁的时候要唤醒被挂起的线程。

    • 为什么会尝试CAS不成功以及什么情况下会不成功?

      CAS本身是不带锁机制的,其是通过比较而来。假设如下场景:线程A和线程B都在对象头里的锁标识为无锁状态进入,那么如线程A先更新对象头为其锁记录指针成功之后,线程B再用CAS去更新,就会发现此时的对象头已经不是其操作前的对象HashCode了,所以CAS会失败。也就是说,只有两个线程并发申请锁的时候会发生CAS失败。

      然后线程B进行CAS自旋,等待对象头的锁标识重新变回无锁状态或对象头内容等于对象HashCode(因为这是线程B做CAS操作前的值),这也就意味着线程A执行结束(参见后面轻量级锁的撤销,只有线程A执行完毕撤销锁了才会重置对象头),此时线程B的CAS操作终于成功了,于是线程B获得了锁以及执行同步代码的权限。如果线程A的执行时间较长,线程B经过若干次CAS时钟没有成功,则锁膨胀为重量级锁,即线程B被挂起阻塞、等待重新调度。

重量级锁

  • 背景:依赖操作系统的互斥锁Mutex Lock(通常由操作系统提供,使用系统调用来实现线程的阻塞和唤醒)来实现。操作系统实现线程之间的切换这就需要从用户态转换到核心态,成本非常高,状态之间的转换需要相对比较长的时间。

重量级锁、轻量级锁和偏向锁之间转换

img

img

CAS的本地延迟

  • SMP(对称多处理器)架构:

    其意思是 所有的CPU会共享一条系统总线(BUS),靠此总线连接主存。每个核都有自己的一级缓存,各核相对于BUS对称分布,因此这种结构称为“对称多处理器”。

    • Core1 ~ Core6都有自己的一级缓存,当每个Core从主存Memory Load一个value到自己的L1 Cache中,然后Core1修改value并Store到主存Memory的时候,会通过BUS总线通知其他Core此值失效(称为Cache命中缺失),其他Core接到通知不得不从Memory重新Load这个Value。这种通过总线的来回通信称为“Cache一致性流量”。当所有Core的Value都一致是称为“Cache一致性”。总线被设计为固定的“通信能力”,如果Cache一致性流量过大,总线将成为瓶颈。锁设计的终极目标便是减少Cache一致性流量。
    • CAS恰好会导致Cache一致性流量。如果有很多线程都共享同一个对象,当某个Core CAS成功时必然会引起总线风暴,导致本地延迟。

Lock Record

  1. 线程进入同步块时,如果同步对象锁状态为无锁状态(锁标志位为“01”状态,是否为偏向锁为“0”),虚拟机首先将在当前线程的栈帧中建立一个名为锁记录(Lock Record)的空间,用于存储锁对象目前的Mark Word的拷贝,官方称之为 Displaced Mark Word
  2. 拷贝对象头中的Mark Word复制到锁记录(Lock Record)中;
  3. 拷贝成功后,虚拟机将使用CAS操作尝试将对象Mark Word中的Lock Word更新为指向当前线程Lock Record的指针,并将Lock record里的owner指针指向object mark word。如果更新成功,则执行步骤(4),否则执行步骤(5);
  4. 如果这个更新动作成功了,那么当前线程就拥有了该对象的锁,并且对象Mark Word的锁标志位设置为“00”,即表示此对象处于轻量级锁定状态,此时线程堆栈与对象头的状态如下图所示:
  5. 如果这个更新操作失败了,虚拟机首先会检查对象Mark Word中的Lock Word是否指向当前线程的栈帧,如果是,就说明当前线程已经拥有了这个对象的锁,那就可以直接进入同步块继续执行。否则说明多个线程竞争锁,进入自旋执行(3),若自旋结束时仍未获得锁,轻量级锁就要膨胀为重量级锁,锁标志的状态值变为“10”,Mark Word中存储的就是指向重量级锁(互斥量)的指针,当前线程以及后面等待锁的线程也要进入阻塞状态。

总结

  • 锁不是互相替代,而是根据不同场景选择不同锁。
  • 锁只能升级,不能降级。升级过程中开销逐渐增大。
    • 如果是单线程使用,那偏向锁毫无疑问代价最小,并且它就能解决问题,连CAS都不用做,仅仅在内存中比较下对象头就可以了;
    • 如果出现了其他线程竞争,则偏向锁就会升级为轻量级锁;
    • 如果其他线程通过一定次数的CAS尝试没有成功,则进入重量级锁;

img

代码测试

  • 先不加锁,后加锁 001无锁-00轻量级锁

image

  • Sleep 3秒 00轻量级锁

image

  • Sleep 5秒 101偏向锁

image

参考引用

图文详解Java对象内存布局
谈谈JVM内部锁升级过程
深入分析Synchronized原理(阿里面试题)
不可不说的Java“锁”事

标签:Word,synchronized,CAS,Mark,对象,线程,原理,轻量级
From: https://www.cnblogs.com/wftop1/p/14979420.html

相关文章

  • Spring容器系列-启动原理(下)
    Spring容器系列-启动原理(下)  从上一篇文章《Spring容器系列-启动原理(上)》中,介绍了Spring容器初始化的核心方法refresh()的整体情况。这篇文章来详细展开介绍。其中比较重要的方法会标记上***  一、refresh中的12个方法  1. prepareRefresh  主要作用:记录下容器......
  • Elasticsearch 近实时搜索的底层原理
    我们都知道Elasticsearch的搜索是近实时的,数据写入后,立即搜索(不通过id)文档是搜不到的。这一切的原因要归于lucene所提供的API,因为lucene的API就是非实时的,Elasticsearch在lucene之上盖房子,通过一些增强,实现了查询的近实时和id查询的实时性。本文就来看看这个近实时......
  • AOP切面的实现原理【底层源码】
    AOP是基于IOC的Bean加载来实现的,将切面类的所有切面方法根据使用的注解生成对应的Advice,并将Advice连同切入点匹配器和切面类等信息一并封装到Advisor,为后续交给代理增强实现做准备这里我们可以很明确的知道,AOP也是在Bean容器中被Spring管理的,根据初始化过程打断点定位......
  • 数据库原理(关系数据库规范化理论)——(4)
    一、关系模式规范化的必要性1.关系可能出现的问题数据冗余大;插入异常;删除异常;更新异常;2.关系模式应满足的基本要求元组的每个分量必须是不可分割的数据项;数据库中的数据冗余应尽可能少;不要出现插入异常;不要出现删除异常;不要出现更新异常;数据库设计应考虑查询要求,数据组织要......
  • 深入解读Netty中的NIO:原理、架构与实现详解
    深入解读Netty中的NIO:原理、架构与实现详解Netty是一个基于Java的异步事件驱动网络应用框架,广泛用于构建高性能、高可扩展性的网络服务器和客户端。Netty的核心是基于JavaNIO(Non-blockingI/O)的,因此理解Netty的实现需要先了解JavaNIO的基本概念和机制。JavaNIO简介Jav......
  • 深入浅出Netty:高性能网络应用框架的原理与实践
    深入浅出Netty:高性能网络应用框架的原理与实践1.Netty简介Netty是一个基于Java的异步事件驱动的网络应用框架,广泛用于构建高性能、高可扩展性的网络服务器和客户端。它提供对多种协议(如TCP、UDP、SSL等)的支持,适用于各种网络通信场景。2.核心组件Channel:代表一个到远程......
  • Spring容器系列-启动原理
    Spring容器系列-启动原理  Spring具有非常庞大的体系,但无论多大都是由一个个小的模块组合而来,不管是SpringMvc还是SpringBoot,都是在Spring的基础上延伸而来,因此,看源码要找对方向。  我们知道,在SpringBoot之前,对象的管理和配置都是通过XML的方式来实现的,那么Spring是......
  • AI辅助编程2 AI编码技术的工作原理
    2AI编码技术的工作原理在本章中,我们将揭开人工智能辅助编程工具的神秘面纱,了解它们的工作原理。我们将简要回顾一下历史,体验一下变换器模型和LLM,并演示OpenAIPlayground。然后,我们将获得一些关于如何评估LLM的建议。掌握这项强大的技术能做什么、不能做什么,将为在实际软件项目......
  • 1、docker-安装-阿里云镜像加速-docker工作流程和底层原理
    1、访问官网:https://docs.docker.com/get-docker/2、卸载旧版本:yumremovedocker\docker-client\docker-client-latest\docker-common\docker-latest\docker-latest-lo......
  • 6、docker-docker的图形化web界面管理工具-portainer--docker镜像原理
    图形化界面管理工具-portainer-可以提供后台面板供我们操作1、下载启动portainer·#-v挂载-v/var/run/docker.sock:/var/run/docker.sock 表示将主机上的Docker守护进程的Unix套接字文件映射到容器内部,这样Portainer就可以通过DockerAPI与Docker守护进程交互了。......