synchronized 使用及实现原理

news2024/11/25 14:42:03

synchronized 关键字

如何使用

synchronized 关键字的使用方式主要有下面 3 种:

  1. 修饰实例方法

  2. 修饰静态方法

  3. 修饰代码块

1、修饰实例方法 (锁当前对象实例)

给当前对象实例加锁,进入同步代码前要获得 当前对象实例的锁

synchronized void method() {
    //业务代码
}

此时,synchronized加锁的对象就是这个方法所在实例的本身。

2、修饰静态方法 (锁当前类)

给当前类加锁,会作用于这个类的所有对象实例

这是因为静态成员不属于任何一个实例对象,归整个类所有,不依赖于类的特定实例,被类的所有实例共享。

synchronized static void method() {
    //业务代码
}

3、修饰代码块 (锁指定对象/类)

对括号里指定的对象/类加锁:

  • synchronized(object) 给对象加锁

  • synchronized(类.class) 给类加锁

synchronized(this) {
    //业务代码
}

 


synchronized关键字是如何对一个对象加锁实现代码同步的呢?

synchronized原理

synchronized 底层实现原理

底层原理就是,通过monitorenter 和 monitorexit 来完成同步机制,所以我们不用通过 lock 和 unlock 实现上锁和释放锁的过程。

当 synchronized 修饰方法的时候,JVM 采用ACC_SYNCHRONIZED标记符来实现同步,这个标识指明了该方法是一个同步方法。

synchronized修饰同步方法


在JVM中,对象在内存中存储的布局可以分为三个区域,分别是对象头、实例数据以及填充数据。

  • 实例数据 存放类的属性数据信息,包括父类的属性信息,这部分内存按4字节对齐。

  • 填充数据 由于虚拟机要求对象起始地址必须是8字节的整数倍。填充数据不是必须存在的,仅仅是为了字节对齐。

  • 对象头 在HotSpot虚拟机中,对象头又被分为两部分,分别为:Mark Word(标记字段)、Class Pointer(类型指针)。如果是数组,那么还会有数组长度。对象头是本章内容的重点,下边详细讨论。

对象头

在对象头的Mark Word中主要存储了对象自身的运行时数据,例如哈希码、GC分代年龄、锁状态、线程持有的锁、偏向线程ID以及偏向时间戳等。同时,Mark Word也记录了对象和锁有关的信息。

当对象被synchronized关键字当成同步锁时,和锁相关的一系列操作都与Mark Word有关。Mark Word在不同锁状态下存储的内容有所不同。

GC标记是垃圾回收机制

可以看到重量级锁对象头的MarkWord中存储了指向Monitor对象的指针,那么什么是Monitor?

Monitor对象

Monitor对象被称为管程或者监视器锁。在Java中,每一个对象实例都会关联一个 Monitor 对象。这个Monitor对象既可以与对象一起创建销毁,也可以在线程试图获取对象锁时自动生成。当这个Monitor对象被线程持有后,它便处于锁定状态。

Monitor是由ObjectMonitor实现的,它是一个使用C++实现的类,主要数据结构如下:

ObjectMonitor() {
    _header       = NULL;
    _count        = 0; //记录个数
    _waiters      = 0,
    _recursions   = 0;  // 线程重入次数
    _object       = NULL;
    _owner        = NULL;
    _WaitSet      = NULL; // 调用wait方法后的线程会被加入到_WaitSet
    _WaitSetLock  = 0 ;
    _Responsible  = NULL ;
    _succ         = NULL ;
    _cxq          = NULL ; // 阻塞队列,线程被唤醒后根据决策判读是放入cxq还是EntryList
    FreeNext      = NULL ;
    _EntryList    = NULL ; // 没有抢到锁的线程会被放到这个队列
    _SpinFreq     = 0 ;
    _SpinClock    = 0 ;
    OwnerIsThread = 0 ;
  }
 

ObjectMonitor中有五个重要部分,分别为ower,WaitSet,cxq,EntryList和count。

  • _ower 用来指向持有monitor的线程,它的初始值为NULL,表示当前没有任何线程持有monitor。当一个线程成功持有该锁之后会保存线程的ID标识,等到线程释放锁后_ower又会被重置为NULL;

  • _WaitSet 调用了锁对象的wait方法后的线程会被加入到这个队列中;如果调用锁对象的wait()方法,线程会释放当前持有的monitor,并将owner变量重置为NULL,且count减1,同时该线程会进入到_WaitSet集合中等待被唤醒。

  • _cxq 是一个阻塞队列,线程被唤醒后根据决策判读是放入cxq还是EntryList;

  • _EntryList 没有抢到锁的线程会被放到这个队列;

  • count 用于记录线程获取锁的次数,成功获取到锁后count会加1,释放锁时count减1。

问题:synchronized 锁住的到底是什么?

答:是 monitor enter 和 monitor exit

MarkWord 中存储了指向 Monitor 对象的指针,Monitor 是由ObjectMonitor 实现

ObjectMonitor 原理

当获取 monitor 对象的线程进入 _ower 区的时候, _count+1。当调用 wait()方法后,释放 monitor 对象, _count - 1,退出 _ower 区。

当对象的Monitor的计数器count为0 的时候,线程可以取得Monitor,并将count设置为1,即获得该对象的锁,表示只有这个线程可以对该对象进行操作。如果当前线程已经拥有该对象monitor的持有权,那它可以重入这个 monitor ,计数器的值也会加 1。而当执行monitorexit指令时,锁的计数器会减1。

如果对象的Monitor的count为1时,那么当前线程获取锁失败将被阻塞并进入到_EntryList中,直到等待的锁被释放为止。也就是说,当所有相应的monitorexit指令都被执行,计数器的值减为0,执行线程将释放 monitor(锁),其他线程才有机会持有 monitor 。

流程

执行 monitorenter 获取锁

所以当了解完原理后,就能知道 monitor 是与同步有关系,所以synchronized 锁住的是:

  • monitorenter,在判断拥有同步标识 ACC_SYNCHRONIZED 抢先进入此方法的线程会优先拥有 Monitor 的 owner ,此时计数器 +1。

  • monitorexit,当执行完退出后,计数器 -1,归 0 后被其他进入的线程获得。

锁的升级

无锁->偏向锁->轻量级锁->重量级锁

锁只能由这个方向进行升级,不可以逆着来。

偏向锁

由于大部分情况下啊,一般都是同一线程反复获得锁,每一次获得锁的过程中,需要获得锁和解锁的操作,所以,如果只有一个线程的话,那可以创建一种锁,当线程获得锁之后,就不进行解锁操作了,每次这个线程进入或退出同步快的时候,只需要比较下线程id就可以了

偏向锁加锁

当一个线程访问同步块的时候(同一时刻只有一个线程能执行同步块之中的代码),会在对象头和栈帧中存储锁偏向的线程id,如果该线程再进入的时候,只需要比较下线程id就可以。

偏向锁撤销
  • 如果原来持有偏向锁线程已经 退出同步代码块或者已经死亡,那么偏向锁可以直接撤销,转变为无锁状态。

  • 如果还在代码块之中,那么偏向锁升级为轻量级锁,原来的线程仍持有锁,其他进程需要cas,来竞争锁

优点

保证在只有一个线程的情况下,线程获得锁之后,该线程进入或退出同步代码块不需要进行cas操作,如果有多个锁竞争,那么锁会进行升级。

轻量级锁

轻量级锁是一种Java中实现线程同步的锁,它比重量级锁更高效,但也需要一定的条件才能使用。轻量级锁的加锁和撤销过程如下:

加锁过程

当一个线程要执行同步代码块时,它会先在自己的栈帧中创建一个锁记录,然后把对象头中的Mark Word复制到锁记录中,这叫做Displaced Mark Word。接着,线程会用CAS操作尝试把对象头中的Mark Word替换成指向锁记录的指针。如果成功,说明线程获得了轻量级锁,可以继续执行同步代码块。如果失败,说明有其他线程也在竞争这个锁,那么当前线程就会进行自旋,即不断重试CAS操作,直到成功或者超过一定次数。

撤销过程:

当一个线程执行完同步代码块时,它会用CAS操作尝试把对象头中的Mark Word恢复成原来的Displaced Mark Word。如果成功,说明没有其他线程竞争该锁,轻量级锁就被释放了。如果失败,说明有其他线程在自旋等待该锁,那么当前线程就会通知其他线程停止自旋,然后把锁升级为重量级锁,释放该锁后唤醒一个等待的线程。

具体CAS过程

CAS(Compare And Swap)是一种原子操作,它可以保证多个线程同时对同一个内存地址进行更新时的正确性。CAS操作需要三个参数:内存地址、期望值和新值。

CAS操作的过程是这样的:

  • 首先,从内存地址中读取当前值,与期望值进行比较,如果相等,说明没有其他线程修改过该内存地址,那么就用新值替换当前值,并返回成功。

  • 如果不相等,说明有其他线程修改过该内存地址,那么就放弃替换,并返回失败。

在轻量级锁的加锁和撤销过程中,CAS操作的内存地址就是对象头中的Mark Word,期望值就是Displaced Mark Word,新值就是指向锁记录的指针或者原来的Displaced Mark Word。CAS操作可以保证只有一个线程能成功获取或释放轻量级锁,其他线程则需要自旋或者阻塞。

轻量级锁优缺点

轻量级锁的优点是可以避免线程的阻塞和切换,因为得不到锁的线程不会被挂起,而是进行自旋,如果锁释放,可以第一时间知道,提高程序的响应速度。轻量级锁的缺点是如果一直不能获取到锁,长时间的自旋会造成CPU消耗。轻量级锁适用于少量线程竞争锁对象,且线程持有锁的时间不长,追求响应速度的场景。

总结

synchronized 同步语句块的实现使用的是 monitorentermonitorexit 指令,其中 monitorenter 指令指向同步代码块的开始位置,monitorexit 指令则指明同步代码块的结束位置。

synchronized 修饰的方法并没有 monitorenter 指令和 monitorexit 指令,取得代之的确实是 ACC_SYNCHRONIZED 标识,该标识指明了该方法是一个同步方法。

不过两者的本质都是对对象监视器 monitor 的获取。

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

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

相关文章

【opencv】图像处理(二)

前文指引 一、使用到的图片 一、梯度计算 原始图片 img cv2.imread(circle.jpg)plt.imshow(img) plt.show()sobel算子 使用两个核 Gx [[-1,0,1], [-2,0,2], [-1,0,1]] Gy [[-1,-2,-1], [0,0,0], [1,2,1]] dst cv2.Sobel(src, ddepth, dx, dy, ksize) ddepth 深度 -1d…

添砖Java之路(其四)——面向对象的编程,类和对象

目录 前言: 面向对象的编程: this关键字: 构造方法: 前言: 其实中间我还有很多地方没有去讲,因为我觉得里面的很多东西和c/c差不太多,就比如逻辑运算,方法重载,以及数…

庙算兵棋推演AI开发初探(4-调用AI模型)

前面讲了如何开展编写规则脚本型Agent(智能体)的方法,现在探究一下如何调用知识型(一般而言的训练出的模型)智能体的方法。 这次调用的是庙算平台的demo(网址见图) 下载了“知识强化学习型”…

详解依赖注入的三种方法以及遇到问题的解决

各位大佬光临寒舍,希望各位能赏脸给个三连,谢谢各位大佬了!!! 目录 1.三种依赖注入的方法 1.属性注入 优点 缺点 2.构造方法注入 优点 缺点 3.Setter注入 优点 缺点 4.小结 2.依赖注入常见问题的解决 1…

全国防灾减灾日主题活动投稿我可算找对了投稿方法

作为一名社区公众人员,我深知对外信息宣传的重要性。特别是在全国防灾减灾日这样的特殊时刻,我们不仅要向居民普及防灾减灾知识,还要通过媒体将社区的活动和成果展示给更多人。然而,在投稿的过程中,我最初却遭遇了诸多挑战。 起初,我采用传统的邮箱投稿方式,将精心撰写的稿件发…

网页如何集成各社区征文活动

Helllo , 我是小恒 由于我需要腾讯云社区,稀土掘金以及CSDN的征文活动RSS,找了一下没发现,所以使用GET 请求接口对网页定时进行拉取清洗,甚至无意间做了一个简单的json格式API 最终网址:hub.liheng.work API:http://hub.liheng.wo…

ubuntu server 22.04.4 系统安装详细教程

本教程使用vmware workstation 17创建虚拟机进行安装演示,安装方式和真机安装没有区别。 1、下载镜像 下载ubuntu server版本系统镜像,官网下载地址:https://cn.ubuntu.com/download/server/step1 注意:自己下载时需要确认是否是…

向银行家应用程序添加日期

● 首先我们将下面图片上的时间更换成现在的时间 const now new Date(); const day now.getDate(); const month now.getMonth() 1; const year now.getFullYear(); const hour now.getHours(); const min now.getMinutes();labelDate.textContent ${day}/${month}/$…

从头开始学Spring—01Spring介绍和IOC容器思想

目录 1.Spring介绍 1.1Spring概述 1.2特性 1.3五大功能模块 2.IOC容器 2.1IOC思想 ①获取资源的传统方式 ②反转控制方式获取资源 ③DI 2.2IOC容器在Spring中的实现 ①BeanFactory ②ApplicationContext ③ApplicationContext的主要实现类 1.Spring介绍 1.1Sprin…

ASP.NET Web Api 如何使用 Swagger 管理 API

前言 Swagger 是一个开源的框架,支持 OpenAPI 规范,可以根据 API 规范自动生成美观的、易于浏览的 API 文档页面,包括请求参数、响应示例等信息,并且,Swagger UI 提供了一个交互式的界面,可以帮助我们快速…

本来还挺喜欢……

前阵子买了个天空星开发板,到手之后发觉不对劲。 之前我们玩玩开发板都是用的面包板的,就算是ESP那种比较宽的板子用两个面包板拼一下也勉强可以用。 但是天空星它的引脚是分为两组,每组有两排,如果我们还是直接使用面包板的话&a…

Pencils Protocol 提供层次化的 Staking,品牌升级不断

Pencils Protocol 是一个 Scroll 生态中的一个综合应用平台,在全新的品牌升级后(原为 Penpad),其在原有的 LaunchPad 的基础上,进一步向收益聚合器、RWA 等板块进行全新的拓展。目前,Pencils Protocol 生态的整体功能板块包括 Lau…

Kubernetes——两万字超细致集群搭建平台规划

目录 前言——常见的K8S安装部署方式 一、Kubernetes平台规划 1.单Master集群架构 2.多Master集群架构 二、集群规划 1.服务器硬件配置推荐 2.操作系统初始化 2.1关闭防火墙 2.2关闭SElinux 2.3关闭Swap 2.4添加Hosts 2.5调整内核参数 2.5同步时间 三、集群搭建…

【每日刷题】Day39

【每日刷题】Day39 🥕个人主页:开敲🍉 🔥所属专栏:每日刷题🍍 🌼文章目录🌼 1. 622. 设计循环队列 - 力扣(LeetCode) 2. 387. 字符串中的第一个唯一字符 - …

C++类细节,反汇编,面试题02

文章目录 2. 虚函数vs纯虚函数3. 重写vs重载vs隐藏3.1. 为什么C可以重载? 4. struct vs union4.1. 为什么要内存对齐? 5. static作用6. 空类vs空结构体6.1. 八个默认函数:6.2. 为什么空类占用1字节 7. const作用7.1 指针常量vs常量指针vs常量…

k8s v1.20二进制部署

目录 一、环境准备 二、操作系统初始化配置 2.1.关闭防火墙 ​编辑 2.2.关闭selinux 2.3.关闭swap 2.4.根据规划设置主机名 2.5在master添加hosts 2.6.调整内核参数 2.7.时间同步 三、部署 docker引擎 3.1.所有 node 节点部署docker引擎 四、部署 etcd 集群 4.1.…

【云计算小知识】云管理的作用是什么?

云计算已经成为推动企业数字化转型,提升运营效率的重要力量。而在这个过程中,云管理作为确保云计算环境稳定、高效运行的关键环节,其作用愈发凸显。今天我们小编就给大家详细介绍一下云管理的作用是什么? 云管理的作用是什么&…

找不到mfc140.dll是什么意思?四种高效率方法修复mfc140.dll文件

软件运行过程中的错误和问题偶尔会发生,这可能导致不便和工作效率的降低。其中一个常见的问题是“找不到 mfc140.dll”错误消息,这会阻止某些基于 Microsoft Visual Studio 2015 编写的应用程序运行。mfc140.dll 是一个重要的系统文件,今天我…

Whistle Web Debugging Proxy介绍及使用

大家好,今天继续给大家分享一款抓包工具,这款抓包工具是网页的形式,方便多人访问同时维护。Whistle Web Debugging Proxy是一个用于HTTP、HTTPS、WebSocket等网络协议的跨平台调试工具。它可以帮助开发者对网络请求进行捕捉、分析、修改和重定…

Spring Boot项目怎么集成Gitee登录

一、背景 现在的越来越多的项目,需要集成第三方系统进行登录。今天我们以Spring Boot项目集成Gitee为例,演示一下怎么使用Oauth2协议,集成第三方系统登录。 不了解oauth2的,可以看我之前的文章。Ouath2是怎么实现在第三方应用认…