Java 并发编程(三)Synchronized底层优化(偏向锁与轻量级锁)

2024-04-25 23:48

本文主要是介绍Java 并发编程(三)Synchronized底层优化(偏向锁与轻量级锁),希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

Synchronized低效的原因

在Java SE 1.6发布前,使用Synchronized关键字实现同步功能是比较低效的,很多人称其为重量级锁.究其原理,是因为Synchronized是通过对象内部的一个叫做监视器锁(monitor)来实现的,而监视器锁本质又是依赖于底层的操作系统的Mutex Lock来实现的。操作系统实现线程之间的切换需要从用户态转换到核心态,这个成本非常高,状态之间的转换需要相对比较长的时间,这就是为什么Synchronized效率低的原因。

Java SE 1.6为Synchronized带来的优化(偏向锁和轻量级锁)

Java SE 1.6中为了减少获得锁和释放锁带来的性能消耗,引入了偏向锁和轻量级锁.要想弄清楚这两个锁的原理首先要了解Java对象头,因为Synchronized用的锁就存在Java对象头中.

Java对象头

Java对象头的主要内容

内容说明
Mark Work存储对象的hashCode或锁信息等
Class Metadata Address存储到对象类型数据的地址(即指向该对象的类型数据的指针)
Array Length数组的长度(如果当前对象是数组)

32位的JVM的Mark Work的默认存储结构

锁状态25bit4bit1bit 是否为偏向锁2bit 锁标志位
无锁状态对象的hashCode对象分代年龄001

Mark Word存储结构中的数据会随着标志位的变化而变化.
Mark Word可能的状态变化
image
在Java SE 1.6中,锁一共有4中状态,级别从低到高依次为:

  • 无锁状态
  • 偏向锁
  • 轻量级锁
  • 重量级锁

值得注意的是锁状态只能升级不能降级,也就是说轻量级可以膨胀为重量级锁,但是这个过程不可以逆转.

1.偏向锁

为何引入偏向锁

大多数情况下,锁不仅不存在多线程竞争,而且总是由同一个线程多次获得.
引入偏向锁是为了在无多线程竞争的情况下尽量减少不必要的轻量级锁执行路径,因为轻量级锁的获取及释放依赖多次CAS原子指令,而偏向锁只需要在置换ThreadID的时候依赖一次CAS原子指令(由于一旦出现多线程竞争的情况就必须撤销偏向锁,所以偏向锁的撤销操作的性能损耗必须小于节省下来的CAS原子指令的性能消耗)
偏向锁是在只有一个线程执行同步块时进一步提高性能

偏向锁的获取过程:
  1. 访问对象头的Mark Word中的锁标志位是否为01(确认为可偏向状态)
  2. 如果为可偏向状态,则测试对象头中Mark Word的线程ID是否指向当前线程,如果是则进入步骤5,否则进入步骤3
  3. 如果对象头中Mark Word的线程ID并未指向当前线程,则当前线程通过CAS操作竞争锁.如果竞争成功,则将Mark Word中线程ID设置为当前ID,然后执行步骤5.如果竞争失败,则执行步骤4.
  4. 如果CAS竞争偏向锁失败,则表示有竞争.当到到全局安全点时获得偏向锁的线程被挂起,偏向锁升级为轻量级锁,然后被阻塞在安全点的线程继续往下执行同步代码.
  5. 执行同步代码

注意:第四步中到达安全点safepoint会导致stop the word,时间很短。

偏向锁的撤销

使用一种等到竞争出现才释放锁的机制,即当其他线程竞争偏向锁时,持有偏向锁的线程会运行到全局安全点后挂起,判断锁对象是否处于被锁定状态,撤销偏向锁后恢复到未锁定(标志位为“01”)或轻量级锁(标志位为“00”)的状态。

2.轻量级锁

为何引入轻量级锁

轻量级锁是为了在没有多线程竞争的前提下,减少传统的重量级锁使用产生的性能消耗。轻量级锁所适应的场景是线程交替执行同步块的情况,如果存在同一时间访问同一锁的情况,就会导致轻量级锁膨胀为重量级锁。

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


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

3.锁的优缺点对比

优点缺点适用场景
偏向锁加锁和解锁不需要额外的消耗,和执行非同步方法比时间相差无几如果线程存在锁竞争,会带来额外的锁撤销的消耗适用于只有一个线程访问同步块场景
轻量级锁竞争的线程不会阻塞,提高了程序响应速度如果始终得不到锁竞争的线程,使用自旋会消耗CPU追求响应速度,同步块执行速度非常快
重量级锁线程竞争不会自旋,不会消耗CPU线程阻塞,响应速度慢追求吞吐量,同步块执行速度慢或者执行时间长.

Java 并发编程(一)Volatile原理剖析及使用
Java 并发编程(二)Synchronized原理剖析及使用
Java 并发编程(三)Synchronized底层优化(偏向锁与轻量级锁)
Java 并发编程(四)JVM中锁的优化
Java 并发编程(五)原子操作类

这篇关于Java 并发编程(三)Synchronized底层优化(偏向锁与轻量级锁)的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/936131

相关文章

Spring Cloud Hystrix原理与注意事项小结

《SpringCloudHystrix原理与注意事项小结》本文介绍了Hystrix的基本概念、工作原理以及其在实际开发中的应用方式,通过对Hystrix的深入学习,开发者可以在分布式系统中实现精细... 目录一、Spring Cloud Hystrix概述和设计目标(一)Spring Cloud Hystr

Spring Boot整合消息队列RabbitMQ的实现示例

《SpringBoot整合消息队列RabbitMQ的实现示例》本文主要介绍了SpringBoot整合消息队列RabbitMQ的实现示例,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的... 目录RabbitMQ 简介与安装1. RabbitMQ 简介2. RabbitMQ 安装Spring

springMVC返回Http响应的实现

《springMVC返回Http响应的实现》本文主要介绍了在SpringBoot中使用@Controller、@ResponseBody和@RestController注解进行HTTP响应返回的方法,... 目录一、返回页面二、@Controller和@ResponseBody与RestController

JAVA集成本地部署的DeepSeek的图文教程

《JAVA集成本地部署的DeepSeek的图文教程》本文主要介绍了JAVA集成本地部署的DeepSeek的图文教程,包含配置环境变量及下载DeepSeek-R1模型并启动,具有一定的参考价值,感兴趣的... 目录一、下载部署DeepSeek1.下载ollama2.下载DeepSeek-R1模型并启动 二、J

springboot rocketmq配置生产者和消息者的步骤

《springbootrocketmq配置生产者和消息者的步骤》本文介绍了如何在SpringBoot中集成RocketMQ,包括添加依赖、配置application.yml、创建生产者和消费者,并展... 目录1. 添加依赖2. 配置application.yml3. 创建生产者4. 创建消费者5. 使用在

Spring Retry 实现乐观锁重试实践记录

《SpringRetry实现乐观锁重试实践记录》本文介绍了在秒杀商品SKU表中使用乐观锁和MybatisPlus配置乐观锁的方法,并分析了测试环境和生产环境的隔离级别对乐观锁的影响,通过简单验证,... 目录一、场景分析 二、简单验证 2.1、可重复读 2.2、读已提交 三、最佳实践 3.1、配置重试模板

Spring中@Lazy注解的使用技巧与实例解析

《Spring中@Lazy注解的使用技巧与实例解析》@Lazy注解在Spring框架中用于延迟Bean的初始化,优化应用启动性能,它不仅适用于@Bean和@Component,还可以用于注入点,通过将... 目录一、@Lazy注解的作用(一)延迟Bean的初始化(二)与@Autowired结合使用二、实例解

SpringBoot使用Jasypt对YML文件配置内容加密的方法(数据库密码加密)

《SpringBoot使用Jasypt对YML文件配置内容加密的方法(数据库密码加密)》本文介绍了如何在SpringBoot项目中使用Jasypt对application.yml文件中的敏感信息(如数... 目录SpringBoot使用Jasypt对YML文件配置内容进行加密(例:数据库密码加密)前言一、J

Java中有什么工具可以进行代码反编译详解

《Java中有什么工具可以进行代码反编译详解》:本文主要介绍Java中有什么工具可以进行代码反编译的相关资,料,包括JD-GUI、CFR、Procyon、Fernflower、Javap、Byte... 目录1.JD-GUI2.CFR3.Procyon Decompiler4.Fernflower5.Jav

Spring Boot 中正确地在异步线程中使用 HttpServletRequest的方法

《SpringBoot中正确地在异步线程中使用HttpServletRequest的方法》文章讨论了在SpringBoot中如何在异步线程中正确使用HttpServletRequest的问题,... 目录前言一、问题的来源:为什么异步线程中无法访问 HttpServletRequest?1. 请求上下文与线