synchronized 实现原理

参考链接

文章目录

  • 一 基本使用
    • 1 三个作用
    • 2 三种用法
  • 二 同步原理
    • 1 监视器 Monitor
    • 2 synchronized 用于同步代码块
    • 3 synchronized 用于同步方法
    • 3 Mark Word
    • 4 对象头的 Mark Word 和线程的 Lock Record
  • 三 锁的优化
    • 1 自旋锁
    • 2 锁消除
    • 3 锁粗化
    • 4 偏向锁
    • 5 轻量级锁、重量级锁以及三种锁的对比*

一 基本使用

1 三个作用

  1. 原子性:确保线程互斥的访问同步代码,即同一时间只有一个线程会进入同步代码块

  2. 可见性:保证共享变量的修改能够及时可见,依赖于 JMM 对一个变量 unlock 操作之前,必须要同步到主内存中;对一个变量进行 lock 操作,则将会清空工作内存(线程私有)中此变量的值,重新从主内存中 load 或 assign 初始化变量值
    在这里插入图片描述

  3. 有序性:虽然进行了重排序,但保证只有一个线程会进入同步代码块,单线程下的指令重排是安全的

2 三种用法

  1. 锁定实例方法时,监视器锁(monitor)便是对象实例(this)
  2. 锁定静态方法时,监视器锁(monitor)便是对象的 Class 实例,即锁定了这个类的所有实例
  3. 锁定对象实例时,监视器锁(monitor)便是括号括起来的对象实例

二 同步原理

数据的同步依赖锁,锁的同步如何实现?

  • synchronized 在软件层面依赖 JVM 实现锁的同步
  • JUC.Lock 在硬件层面依赖特殊的 CPU 指令

1 监视器 Monitor

  • 每个对象都对应一个监视器锁(monitor),用于实现重量级锁
  • synchronized 在 JVM 里的实现都是基于进入和退出 Monitor 对象来实现方法同步和代码块同步
  • 每个 Java 对象的对象头的 Mark Word 中都存放着对应 Monitor 对象的引用,所以任意对象都可以作为锁
  • Monitor 对象包含两个队列 _EntryList_WaitSet ,分别存放未获取到 Monitor 对象的线程,以及曾经获取到并且再次等待 Monitor 对象的线程
    1. 当多个线程同时访问一段同步代码时首先会进入 _EntryList 集合,当线程获取到对象的 monitor 后,进入 _Owner 区域并把 monitor 中的 owner 变量设置为当前线程,同时 monitor 中的计数器 count++
    2. 若线程调用 wait() 方法,将释放当前持有的 monitor,owner 变量恢复为 null,count–,同时该线程进入 _WaitSet 中等待被唤醒
    3. 若当前线程执行完毕,释放 monitor 并复位 count,以便其他线程进入获取 monitor
      在这里插入图片描述

2 synchronized 用于同步代码块

反编译后可以得到由 monitorentermonitorexit 两种指令实现

  • monitorenter:当monitor被占用时就会处于锁定状态,线程执行 monitorenter 指令时尝试获取对象的 monitor 的所有权,过程如下:

    1. 如果 monitor 的进入数为0,则该线程进入monitor,然后将进入数设置为1,该线程即为 monitor 的所有者
    2. 如果线程已经占有该 monitor,只是重新进入,则进入 monitor 的进入数加1 (可重入性)
    3. 如果其他线程已经占用了 monitor,则该线程进入阻塞状态,直到 monitor 的进入数为0,重新尝试获取 monitor 的所有权
  • monitorexit:执行 monitorexit 的线程必须是 monitor 的所有者。指令执行时,monitor的进入数减1,如果减1后进入数为0,那线程退出 monitor,不再是这个 monitor 的所有者。monitorexit 插入在方法结束处和异常处,JVM保证每个 monitorenter 必须有对应的 monitorexit

3 synchronized 用于同步方法

  • 方法的同步并没有通过指令 monitorentermonitorexit 来完成,两种同步方式本质上没有区别,只是方法的同步是一种隐式的方式来实现,无需通过字节码来完成
  • 相对于非同步方法,同步方法的常量池中多了 ACC_SYNCHRONIZED 标示符
  • 当方法调用时,调用指令将会检查方法的 ACC_SYNCHRONIZED 访问标志是否被设置,如果设置了,执行线程将先获取monitor,获取成功之后才能执行方法体,方法执行完后再释放 monitor 。在方法执行期间,其他任何线程都无法再获得同一个monitor 对象

3 Mark Word

在这里插入图片描述

  • Class Pointer(类型指针):对象指向它的类元数据的指针,虚拟机通过这个指针来确定这个对象是哪个类的实例
  • Mark Word(标记字段):用于存储对象自身的运行时数据,是实现轻量级锁偏向锁的关键。- 每个 Java 对象的对象头的 Mark Word 中都存放着对应 Monitor 对象的引用,所以任意对象都可以作为锁。为了在很小的内存中尽可能存储更多的数据,它的结构会随着程序的运行发生变化

4 对象头的 Mark Word 和线程的 Lock Record

  • 在线程进入同步代码块的时候,如果此同步对象没有被锁定,则虚拟机首先在当前线程的栈中创建称为“锁记录(Lock Record)”的空间,这个空间是线程私有的,用于存储锁对象的 Mark Word 的拷贝
  • 每一个被锁住的对象的 Mark Word 都会和获得这个锁的线程的 Lock Record 关联(对象头的 Mark Word 中的 Lock Word 指向 Lock Record 的起始地址)
  • Lock Record 中有一个 Owner 字段存放拥有该锁的线程的唯一标识,表示该锁被这个线程占用

三 锁的优化

1 自旋锁

  • 自旋锁:当一个线程尝试获取某个锁时,如果该锁已被其他线程占用,就一直循环检测锁是否被释放,而不是进入线程挂起或睡眠状态
  • 使用自旋锁的理由:CPU 在用户态和核心态的切换需要消耗资源;大多情况下锁状态的持续时间很短
  • 自旋锁适用于锁保护的临界区很小的情况,临界区很小的话,锁占用的时间就很短。自旋等待不能替代阻塞,虽然它可以避免线程切换带来的开销,但是它占用了CPU处理器的时间,所以要规定适当的自旋次数
  • 适应性自旋锁:线程如果自旋成功了,那么下次自旋的次数会更加多;反之会减少自旋次数甚至取消

2 锁消除

  • 在有些情况下,JVM 检测到不可能存在共享数据竞争,这时会对这些同步锁进行锁消除
  • 在运行这段代码时,JVM 可以明显检测到变量 vector 没有逃逸出方法,所以可以将 vector 内部的加锁操作消除
public void vectorTest(){Vector<String> vector = new Vector<String>();for(int i = 0 ; i < 10 ; i++){vector.add(i + "");}System.out.println(vector);
}

3 锁粗化

  • 如果一系列的连续加锁解锁操作,可能会导致不必要的性能损耗,锁粗化将多个连续的加锁、解锁操作连接在一起,扩展成一个范围更大的锁
  • 上述例子 vector 每次 add 都需要加锁,JVM检测到对同一个对象连续加锁、解锁操作,会合并一个更大范围的加锁、解锁操作,即加锁解锁操作会移到循环之外

4 偏向锁

锁主要存在四种状态,依次是:无锁状态、偏向锁状态、轻量级锁状态、重量级锁状态,锁可以从偏向锁升级到轻量级锁,再升级的重量级锁。但是锁的升级是单向的,也就是说只能从低到高升级,不会出现锁的降级

  • 偏向锁是在单线程执行代码块时使用的机制,如果在多线程并发的环境下一定会转化为轻量级锁或者重量级锁
  • 引入偏向锁主要目的是:为了在没有多线程竞争的情况下尽量减少不必要的轻量级锁执行路径
  • 轻量级锁是为了在线程交替执行同步块时提高性能,而偏向锁则是在只有一个线程执行同步块时进一步提高性能

5 轻量级锁、重量级锁以及三种锁的对比*

  1. 如果是单线程使用,偏向锁的代价最小,仅仅在内存中比较对象头即可,无需 CAS
  2. 如果出现了其他线程竞争,则偏向锁就会升级为轻量级锁
  3. 如果其他线程通过一定次数的 CAS 尝试没有成功,则进入重量级锁
    在这里插入图片描述

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/307115.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

开头th_是什么文件_Python文件读写最详细的讲解

本文来自公众号&#xff1a;可乐的数据分析之路今天这篇文章来详细讲解一下Python中的文件读写。1、文件读写的流程1)类比windows中手动操作txt文档&#xff0c;说明python中如何操作txt文件&#xff1f;① windows中手动操作txt文件的步骤找到word文档打开word文档查看(或操作…

把 Console 部署成 Windows 服务,四种方式总有一款适合你!

一&#xff1a;背景 1. 讲故事上周有一个项目交付&#xff0c;因为是医院级项目需要在客户的局域网独立部署。程序&#xff1a;netcore 2.0&#xff0c;操作系统&#xff1a;windows server 2012&#xff0c;坑爹的事情就来了, netcore sdk 一直装不上&#xff0c;网上找了资料…

AQS ReentrantLock 实现原理

参考链接 文章目录1 AQS (AbstractQuenedSynchronizer)2 Lock 接口与显式条件3 转账 Demo&#xff1a;解决死锁的两种方案4 ReentrantLock 非公平锁加锁流程5 ReentrantLock 和 synchronized 的异同6 ReentrantReadWriteLock1 AQS (AbstractQuenedSynchronizer) 基于 AQS 的同步…

TensorFlow 2学习和工业CV领域应用 心得分享

我是一名来自苏州的机器视觉开发者&#xff0c;从事传统的机器视觉算法开发有11年了&#xff0c;从2018年开始&#xff0c;因为一些复杂微弱的瑕疵检测项目遇到的传统算法瓶颈&#xff0c;开始接触到了深度学习&#xff0c;并选择了使用TensorFlow&#xff0c;期间也是不断摸索…

历史版本_新版本爆料第弹丨英雄练习新去处,荣耀历史秀出来!

《万物有灵》新版本即将到来新版本来临之前妲己宝宝给自己定下了2个小目标&#xff01;via.小五怎么不开心目标一扩展自己小得可怜的英雄勺成为一名拥有英雄海的补位大神目标二通过自己的实力获得N1个响当当的荣耀称号很多召唤师会有疑问&#xff1a;凭妲己宝宝的实力&#xff…

循环遍历多层json_面试官:JSON.stringify() 实现深拷贝有什么问题

为什么要进行深拷贝JS中的变量在内存中存储分为值类型和引用类型&#xff1a; 值类型&#xff1a; 1、占用空间固定&#xff0c;保存在栈中&#xff1b; 2、保存与复制的是值本身&#xff1b; 3、基本类型数据是值类型&#xff08;String,Number,undefined,Boolean,Null&#x…

.NET架构小技巧(6)——什么是好的架构

首先声明&#xff0c;可能本篇文章的含金量配不上这个标题&#xff0c;因为说起架构&#xff0c;可能大家都比较关注高大上的架构&#xff0c;比如分布式的&#xff0c;高并发的&#xff0c;低耦合的&#xff0c;易扩展的等等&#xff0c;本篇可能使你失望了&#xff0c;因为这…

电子工程系庆贺电贺信_创造下一代光电子集成电路

全球互联网正以每年24%的复合速度增长&#xff0c;到2021年将达到每年3.3 zb字节。高速光通信在这个不断连接的世界中是迫切需要的&#xff0c;为了跟上这种增长&#xff0c;光模块的制造的发展是迫切需要的。复旦大学电子工程系博士研究生刘晓研究了集成构成光模块的电子电路和…

禁用笔记本键盘_如何禁用/启用笔记本内置键盘?

有些小伙伴外接了USB键盘想屏蔽掉笔记本的内置键盘&#xff0c;绞尽脑汁都没有办法禁用&#xff0c;其实方法很简单只需要一个简单的命令即可。1、右键点击左下角开始图标(WinX)&#xff0c;选择Windows Powershell(管理员)。2、在打开的窗口中&#xff0c;输入cmd。3、然后输入…

IdentityServer4系列 | 资源密码凭证模式

一、前言从上一篇关于客户端凭证模式中&#xff0c;我们通过创建一个认证授权访问服务&#xff0c;定义一个API和要访问它的客户端&#xff0c;客户端通过IdentityServer上请求访问令牌&#xff0c;并使用它来控制访问API。其中&#xff0c;我们也注意到了在4.x版本中于之前3.x…

深入探究ASP.NET Core Startup的初始化

前言Startup类相信大家都比较熟悉,在我们使用ASP.NET Core开发过程中经常用到的类&#xff0c;我们通常使用它进行IOC服务注册&#xff0c;配置中间件信息等。虽然它不是必须的&#xff0c;但是将这些操作统一在Startup中做处理&#xff0c;会在实际开发中带来许多方便。当我们…

【源码】常用的人脸识别数据库以及上篇性别识别源码

上一篇《使用ML.NET模型生成器来完成图片性别识别》发布后&#xff0c;很多朋友希望得到源码&#xff0c;这里附上地址&#xff1a;https://github.com/xin-lai/GenderRecognition常用的人脸数据库对于部分朋友说&#xff0c;找不到训练的数据&#xff0c;这里也给出部分数据&a…

程序员过关斩将--真的可以用版本号的方式来保证MQ消费消息的幂等性?

灵魂拷问MQ消息的消费为什么有时候要求幂等性&#xff1f;你们都说可以用版本号来解决幂等性消费&#xff1f;什么才是消息幂等性消费的根本性问题&#xff1f;随着系统的复杂性不断增加&#xff0c;多数系统都会引入MQ来进行解耦&#xff0c;其实从引入MQ的初衷来说&#xff0…

spring的钩子_spring提供的钩子,你知道哪些

俗话说得好“工欲善其事必先利其器”&#xff0c;现如今springboot与springcloud已成为快速构建web应用的利器。作为一个爪洼工程师&#xff0c;知道如下的spring扩展点&#xff0c;可能会让你编写出扩展性、维护性更高的代码。spring提供的钩子&#xff0c;你知道哪些bean的生…

.Net 5性能改进

起因在.Net Core跳过4.0,避免和先.Net Framework 4.0同名,版本号变为5.0,同时也不在叫.Net Core改为.Net 5(统一的叫法),先看看官方对.Net版本规划.本文主要是根据https://devblogs.microsoft.com/dotnet/performance-improvements-in-net-5/ 翻译而来.不完全翻译.顺序也有所调…

开放数字世界中的复杂图数据挑战 —— 以教育与开源场景为例

摘要&#xff1a;开源开放的数字世界开始成为时代的潮流&#xff0c;云原生、数据中台、智能PRA开始成为数字世界中的新一代中流砥柱。随着第四范式的普遍流行&#xff0c;各个行业中的数字化转型都会带了海量的具有无限关联的复杂图数据。本报告将以教育与开源两个场景为例&am…

在IIS中部署SPA应用,多么痛的领悟!

目前公司的Web项目是SPA应用&#xff0c;采用前后端分离开发&#xff0c;所以有时也会倒腾Vue框架。“前后端应用最终以容器形态、在k8s中部署, 为此我搭建了基于Gitlab flow的Devops流程。在Devops实践中&#xff0c;容器部署成为良方和事实标准。但是在开发和自测阶段&#x…

mysql闪回工具下载_MySQL闪回工具之myflash 和 binlog2sql

实践利用binlog2sql查询两个binlog之间的SQL&#xff1a;必须是两个binlog日志&#xff0c;指定start-file和stop-filebinlog2sql -h127.0.0.1 -P3309 -udba -pxxxxxx -dsakila -t employee --start-filemysql-bin.000112 --stop-filemysql-bin.000113 > /tmp/db.sql利用bin…

MySQL大表优化方案

背景阿里云RDS FOR MySQL&#xff08;MySQL5.7版本&#xff09;数据库业务表每月新增数据量超过千万,随着数据量持续增加,我们业务出现大表慢查询,在业务高峰期主业务表的慢查询需要几十秒严重影响业务方案概述一、数据库设计及索引优化MySQL数据库本身高度灵活&#xff0c;造成…

使用Azure静态Web应用部署Blazor Webassembly应用

上一次演示了如何使用Azure静态web应用部署VUE前端项目&#xff08;使用Azure静态web应用全自动部署VUE站点&#xff09;。我们知道静态web应用支持VUE&#xff0c;react&#xff0c;angular等项目的部署。除了支持这些常见前端框架&#xff0c;静态web应用同样支持微软推出的最…