JAVA并发-synchronized的底层原理与应用

前言

synchronized作为java关键字,在多线程并发编程中一直是一个很重要的角色,java SE 1.6之前其一直被人称为重量级锁,但是1.6对synchronized进行了各种优化,有些情况下它并没有那么重了。

synchronized 应用方式

利用synchronized实现同步的基础: java中的每一个对象都可以作为锁,具体表现为以下三种形式。

  1. 对于普通同步方法,锁住的是当前实例对象
  2. 对于静态同步方法,锁住的是当前类的class对象
  3. 对于同步方法块,所示synchronized括号里配置的对象

在这里插入图片描述
JVM规范中可以看到Synchronized 在JVM里的实现原理,JVM基于进入和进出Monitor对象来实现方法同步和代码块同步,但是两者的实现细节不一样。

  • 代码块同步

    使用monitorentermonitorexit指令实现的。monitorenter在编译后插入到同步代码块的开始位置,而monitorexit是插入到方法结束处和异常处,JVM保证每个monitorenter必须有monitorexit与值配对。任何对象都有一个monitor与之关联。当且一个monitor被持有后,它将处于锁定状态。线程执行到monitorenter指令时,将会尝试获取对象所对应的monitor的所有权,即尝试获得对象的锁。

  • 方法同步

    细节在JVM规范里没有详细说明。但是方法的同步同样可以使用monitorentermonitorexit指令实现

对象的内存布局

对象在内存中存储的布局可以分为三块区域:对象头(Header)实例数据(Instance Data)对齐填充(Padding)。下图为非数组类型的对象在HotSpot虚拟机中的内存结构(数组对象的对象头会多出一个字宽存储数组的长度):
在这里插入图片描述

理解Java对象头与Monitor

synchronized用的锁是存在Java对象头里的:

  • 如果对象是数组类型,则虚拟机用3个字宽(word)存储对象头。
  • 如果对象是非数组类型,则用2个字宽存储对象头。

32位虚拟机中,1个字宽等于4个字节,即32bit。64位虚拟机中,1个字宽等于64bit,如下表:

长度内容说明
32/64bitMark Word存储对象的HashCode或锁信息等
32/64bitClass Metadata Address存储对象类型数据的指针
32/32bitArray length如果对象是数组类型,则多出此32位保留数组的长度

Java对象头里的Mark Word里默认存储对象的HashCode,分代年龄和锁标记位。32位JVM的Mark Word的默认存储结构如下表所示:

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

在运行期间,Mark Word里存储的数据会随着锁标志位的变化而变化。在32位虚拟机下,Mark Word可能会变化为以下4种数据,如下图:
在这里插入图片描述
在64位虚拟机下,Mark word的变化结构有如下几种:

|------------------------------------------------------------------------------|---------|
|                                  Mark Word (64 bits)                         |   State |
|------------------------------------------------------------------------------|---------|
| unused:25 | identity_hashcode:31 | unused:1 | age:4 | biased_lock:0 | lock:01|   正常  |
|------------------------------------------------------------------------------|---------|
| thread:54 |       epoch:2        | unused:1 | age:4 | biased_lock:1 | lock:01|  偏向锁 |
|------------------------------------------------------------------------------|---------|
|                       ptr_to_lock_record:62                         | lock:00| 轻量级锁 |
|------------------------------------------------------------------------------|---------|
|                     ptr_to_heavyweight_monitor:62                   | lock:10| 重量级锁 |
|------------------------------------------------------------------------------|---------|
|                                                                     | lock:11|  GC标志 |
|------------------------------------------------------------------------------|---------|
锁的升级与对比

Java SE 1.6为了减少获得锁和释放锁带来的性能消耗,引入了“偏向锁”和“轻量级锁”,在 Java SE 1.6中,锁一共有4种状态,级别从低到高依次是:无锁状态偏向锁状态轻量级锁状态重量级锁状态。这几个状态会随着竞争情况逐渐升级。锁可以升级但不能降级,意味着偏向锁升级成轻量级锁后不能降级成偏向锁。这种锁升级却不能降级的策略,目的是为了提高获得锁和释放锁的效率,下文会详细分析。

1.偏向锁

大多数情况下,锁不仅不存在多线程竞争,而且总是由同一线程多次获得,为了让线程获得锁的代价更低而引入了偏向锁。当一个线程访问同步块并获取锁时:

  1. 首先在对象头栈帧中的锁记录里存储锁偏向的线程ID,之后该线程在进入和退出同步块时不需要进行CAS操作来加锁和解锁,只需要简单地测试一下对象头的Mark Word里是否 存储着指向当前线程的偏向锁。
  2. 如果测试成功,表示线程已经获得了锁。
  3. 如果测试失败,则需要再测试一下Mark Word中偏向锁的标识是否设置成1(表示当前是偏向锁)。如果没有设置(值位0),则使用CAS竞争锁;如果设置了(值为1),则尝试使用CAS将对象头的偏向锁指向当前线程。
(1) 偏向锁的撤销

偏向锁使用了一种等到竞争出现才释放锁的机制,所以当其他线程尝试竞争偏向锁时,持有偏向锁的线程才会释放锁。偏向锁的撤销,需要等待全局安全点(在这个时间点上没有正在执行的字节码)。它会首先暂停拥有偏向锁的线程,然后检查持有偏向锁的线程是否活着, 如果线程不处于活动状态,则将对象头设置成无锁状态;如果线程仍然活着,拥有偏向锁的栈会被执行,遍历偏向对象的锁记录,栈中的锁记录和对象头的Mark Word要么重新偏向于其他线程,要么恢复到无锁或者标记对象不适合作为偏向锁,最后唤醒暂停的线程。下图中的线程1演示了偏向锁初始化的流程,线程2演示了偏向锁撤销的流程。
在这里插入图片描述

(2) )关闭偏向锁

偏向锁在Java 6和Java 7里是默认启用的,但是它在应用程序启动几秒钟之后才激活,如 有必要可以使用JVM参数来关闭延迟:-XX:BiasedLockingStartupDelay=0。如果你确定应用程 序里所有的锁通常情况下处于竞争状态,可以通过JVM参数关闭偏向锁:-XX:- UseBiasedLocking=false,那么程序默认会进入轻量级锁状态。

2. 轻量级锁
(1) 轻量级锁加锁

线程在执行同步块之前,会执行以下操作:

  1. JVM会先在当前线程的栈桢中创建用于存储锁记录的空间,
  2. 并将对象头中的Mark Word复制到锁记录中,官方称为Displaced Mark Word。
  3. 然后线程尝试使用CAS将对象头中的Mark Word替换为指向锁记录的指针。如果成功,当前线程获得锁,如果失败,表示其他线程竞争锁,当前线程便尝试使用自旋来获取锁。
(2) 轻量级锁解锁

轻量级解锁时,会使用原子的CAS操作将Displaced Mark Word替换回到对象头,如果成功,则表示没有竞争发生。如果失败,表示当前锁存在竞争,锁就会膨胀成重量级锁。图2-2是 两个线程同时争夺锁,导致锁膨胀的流程图。
在这里插入图片描述
因为自旋会消耗CPU,为了避免无用的自旋(比如获得锁的线程被阻塞住了),一旦锁升级成重量级锁,就不会再恢复到轻量级锁状态。当锁处于这个状态下,其他线程试图获取锁时,都会被阻塞住,当持有锁的线程释放锁之后会唤醒这些线程,被唤醒的线程就会进行新一轮的夺锁之争。

3. 锁的优缺点对比

在这里插入图片描述

参考《java并发编程的艺术》-方腾飞

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
代码下载:完整代码,可直接运行 ;运行版本:2022a或2019b或2014a;若运行有问题,可私信博主; **仿真咨询 1 各类智能优化算法改进及应用** 生产调度、经济调度、装配线调度、充电优化、车间调度、发车优化、水库调度、三维装箱、物流选址、货位优化、公交排班优化、充电桩布局优化、车间布局优化、集装箱船配载优化、水泵组合优化、解医疗资源分配优化、设施布局优化、可视域基站和无人机选址优化 **2 机器学习和深度学习方面** 卷积神经网络(CNN)、LSTM、支持向量机(SVM)、最小二乘支持向量机(LSSVM)、极限学习机(ELM)、核极限学习机(KELM)、BP、RBF、宽度学习、DBN、RF、RBF、DELM、XGBOOST、TCN实现风电预测、光伏预测、电池寿命预测、辐射源识别、交通流预测、负荷预测、股价预测、PM2.5浓度预测、电池健康状态预测、水体光学参数反演、NLOS信号识别、地铁停车精准预测、变压器故障诊断 **3 图像处理方面** 图像识别、图像分割、图像检测、图像隐藏、图像配准、图像拼接、图像融合、图像增强、图像压缩感知 **4 路径规划方面** 旅行商问题(TSP)、车辆路径问题(VRP、MVRP、CVRP、VRPTW等)、无人机三维路径规划、无人机协同、无人机编队、机器人路径规划、栅格地图路径规划、多式联运运输问题、车辆协同无人机路径规划、天线线性阵列分布优化、车间布局优化 **5 无人机应用方面** 无人机路径规划、无人机控制、无人机编队、无人机协同、无人机任务分配 **6 无线传感器定位及布局方面** 传感器部署优化、通信协议优化、路由优化、目标定位优化、Dv-Hop定位优化、Leach协议优化、WSN覆盖优化、组播优化、RSSI定位优化 **7 信号处理方面** 信号识别、信号加密、信号去噪、信号增强、雷达信号处理、信号水印嵌入提取、肌电信号、脑电信号、信号配时优化 **8 电力系统方面** 微电网优化、无功优化、配电网重构、储能配置 **9 元胞自动机方面** 交通流 人群疏散 病毒扩散 晶体生长 **10 雷达方面** 卡尔曼滤波跟踪、航迹关联、航迹融合

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值