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 Boot中WebSocket常用使用方法详解

《SpringBoot中WebSocket常用使用方法详解》本文从WebSocket的基础概念出发,详细介绍了SpringBoot集成WebSocket的步骤,并重点讲解了常用的使用方法,包括简单消... 目录一、WebSocket基础概念1.1 什么是WebSocket1.2 WebSocket与HTTP

SpringBoot+Docker+Graylog 如何让错误自动报警

《SpringBoot+Docker+Graylog如何让错误自动报警》SpringBoot默认使用SLF4J与Logback,支持多日志级别和配置方式,可输出到控制台、文件及远程服务器,集成ELK... 目录01 Spring Boot 默认日志框架解析02 Spring Boot 日志级别详解03 Sp

java中反射Reflection的4个作用详解

《java中反射Reflection的4个作用详解》反射Reflection是Java等编程语言中的一个重要特性,它允许程序在运行时进行自我检查和对内部成员(如字段、方法、类等)的操作,本文将详细介绍... 目录作用1、在运行时判断任意一个对象所属的类作用2、在运行时构造任意一个类的对象作用3、在运行时判断

java如何解压zip压缩包

《java如何解压zip压缩包》:本文主要介绍java如何解压zip压缩包问题,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教... 目录Java解压zip压缩包实例代码结果如下总结java解压zip压缩包坐在旁边的小伙伴问我怎么用 java 将服务器上的压缩文件解压出来,

SpringBoot中SM2公钥加密、私钥解密的实现示例详解

《SpringBoot中SM2公钥加密、私钥解密的实现示例详解》本文介绍了如何在SpringBoot项目中实现SM2公钥加密和私钥解密的功能,通过使用Hutool库和BouncyCastle依赖,简化... 目录一、前言1、加密信息(示例)2、加密结果(示例)二、实现代码1、yml文件配置2、创建SM2工具

Spring WebFlux 与 WebClient 使用指南及最佳实践

《SpringWebFlux与WebClient使用指南及最佳实践》WebClient是SpringWebFlux模块提供的非阻塞、响应式HTTP客户端,基于ProjectReactor实现,... 目录Spring WebFlux 与 WebClient 使用指南1. WebClient 概述2. 核心依

Spring Boot @RestControllerAdvice全局异常处理最佳实践

《SpringBoot@RestControllerAdvice全局异常处理最佳实践》本文详解SpringBoot中通过@RestControllerAdvice实现全局异常处理,强调代码复用、统... 目录前言一、为什么要使用全局异常处理?二、核心注解解析1. @RestControllerAdvice2

Spring IoC 容器的使用详解(最新整理)

《SpringIoC容器的使用详解(最新整理)》文章介绍了Spring框架中的应用分层思想与IoC容器原理,通过分层解耦业务逻辑、数据访问等模块,IoC容器利用@Component注解管理Bean... 目录1. 应用分层2. IoC 的介绍3. IoC 容器的使用3.1. bean 的存储3.2. 方法注

Spring事务传播机制最佳实践

《Spring事务传播机制最佳实践》Spring的事务传播机制为我们提供了优雅的解决方案,本文将带您深入理解这一机制,掌握不同场景下的最佳实践,感兴趣的朋友一起看看吧... 目录1. 什么是事务传播行为2. Spring支持的七种事务传播行为2.1 REQUIRED(默认)2.2 SUPPORTS2

怎样通过分析GC日志来定位Java进程的内存问题

《怎样通过分析GC日志来定位Java进程的内存问题》:本文主要介绍怎样通过分析GC日志来定位Java进程的内存问题,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教... 目录一、GC 日志基础配置1. 启用详细 GC 日志2. 不同收集器的日志格式二、关键指标与分析维度1.