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

相关文章

Vue3 的 shallowRef 和 shallowReactive:优化性能

大家对 Vue3 的 ref 和 reactive 都很熟悉,那么对 shallowRef 和 shallowReactive 是否了解呢? 在编程和数据结构中,“shallow”(浅层)通常指对数据结构的最外层进行操作,而不递归地处理其内部或嵌套的数据。这种处理方式关注的是数据结构的第一层属性或元素,而忽略更深层次的嵌套内容。 1. 浅层与深层的对比 1.1 浅层(Shallow) 定义

JVM 的类初始化机制

前言 当你在 Java 程序中new对象时,有没有考虑过 JVM 是如何把静态的字节码(byte code)转化为运行时对象的呢,这个问题看似简单,但清楚的同学相信也不会太多,这篇文章首先介绍 JVM 类初始化的机制,然后给出几个易出错的实例来分析,帮助大家更好理解这个知识点。 JVM 将字节码转化为运行时对象分为三个阶段,分别是:loading 、Linking、initialization

Spring Security 基于表达式的权限控制

前言 spring security 3.0已经可以使用spring el表达式来控制授权,允许在表达式中使用复杂的布尔逻辑来控制访问的权限。 常见的表达式 Spring Security可用表达式对象的基类是SecurityExpressionRoot。 表达式描述hasRole([role])用户拥有制定的角色时返回true (Spring security默认会带有ROLE_前缀),去

浅析Spring Security认证过程

类图 为了方便理解Spring Security认证流程,特意画了如下的类图,包含相关的核心认证类 概述 核心验证器 AuthenticationManager 该对象提供了认证方法的入口,接收一个Authentiaton对象作为参数; public interface AuthenticationManager {Authentication authenticate(Authenti

Spring Security--Architecture Overview

1 核心组件 这一节主要介绍一些在Spring Security中常见且核心的Java类,它们之间的依赖,构建起了整个框架。想要理解整个架构,最起码得对这些类眼熟。 1.1 SecurityContextHolder SecurityContextHolder用于存储安全上下文(security context)的信息。当前操作的用户是谁,该用户是否已经被认证,他拥有哪些角色权限…这些都被保

Spring Security基于数据库验证流程详解

Spring Security 校验流程图 相关解释说明(认真看哦) AbstractAuthenticationProcessingFilter 抽象类 /*** 调用 #requiresAuthentication(HttpServletRequest, HttpServletResponse) 决定是否需要进行验证操作。* 如果需要验证,则会调用 #attemptAuthentica

Spring Security 从入门到进阶系列教程

Spring Security 入门系列 《保护 Web 应用的安全》 《Spring-Security-入门(一):登录与退出》 《Spring-Security-入门(二):基于数据库验证》 《Spring-Security-入门(三):密码加密》 《Spring-Security-入门(四):自定义-Filter》 《Spring-Security-入门(五):在 Sprin

Java架构师知识体认识

源码分析 常用设计模式 Proxy代理模式Factory工厂模式Singleton单例模式Delegate委派模式Strategy策略模式Prototype原型模式Template模板模式 Spring5 beans 接口实例化代理Bean操作 Context Ioc容器设计原理及高级特性Aop设计原理Factorybean与Beanfactory Transaction 声明式事物

HDFS—存储优化(纠删码)

纠删码原理 HDFS 默认情况下,一个文件有3个副本,这样提高了数据的可靠性,但也带来了2倍的冗余开销。 Hadoop3.x 引入了纠删码,采用计算的方式,可以节省约50%左右的存储空间。 此种方式节约了空间,但是会增加 cpu 的计算。 纠删码策略是给具体一个路径设置。所有往此路径下存储的文件,都会执行此策略。 默认只开启对 RS-6-3-1024k

Java进阶13讲__第12讲_1/2

多线程、线程池 1.  线程概念 1.1  什么是线程 1.2  线程的好处 2.   创建线程的三种方式 注意事项 2.1  继承Thread类 2.1.1 认识  2.1.2  编码实现  package cn.hdc.oop10.Thread;import org.slf4j.Logger;import org.slf4j.LoggerFactory