Synchronized的底层实现

news2024/11/17 15:26:33

Synchronized用法

Synchronized 是 Java 中的一个重要关键字,主要是用来加锁的。在使用Synchronized的时候需要指定一个对象,所以synchornized也被称为对象锁

synchronized 的使用方法比较简单,主要可以用来修饰方法和代码块。根据其锁定的对象不同,可以用来定义同步方法和同步代码块。总的来说有三种用法:

1. 作用在实例方法上

修饰实例方法,相当于对当前实例对象this加锁,this作为对象监视器

public class main{
  public synchronized void hello(){
    System.out.println("hello world");
  }
}

2. 作用在静态方法上

修饰静态方法,相当于对main类的Class对象加锁,main类的Class对象作为对象监视器。

public class main{
  public synchronized static void helloStatic(){
    System.out.println("hello world static");
  }
}

3. 作用在代码块上

指定加锁对象,对给定对象加锁,括号括起来的test对象就是对象监视器。

public class main{
    Object test = new Object();        
    synchronized (test){
        System.out.println("hello world");
    }
}

Synchronized的原理

先说结论:Synchronized的底层原理是通过Monitor对象来完成的

Monitor是什么?

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

Monitor 大致结构如下图所示

Monitor内部具体的存储结构:

Owner:存储当前获取锁的线程,只能有一个线程可以获取

EntryList:存储没有抢到锁的线程,这些都是处于Blocked状态的线程

WaitSet:存储调用了wait方法的线程,这些都是处于Waiting状态的线程

Count:用来记录该对象被线程获取锁的次数,这也说明了synchronized是可重入的

其中在Java虚拟机(HotSpot)中,Monitor是由ObjectMonitor实现的,ObjectMonitor.hpp文件如下所示:

ObjectMonitor() {
    _header       = NULL;
    _count        = 0; // 用来记录该对象被线程获取锁的次数,这也说明了synchronized是可重入的
    _waiters      = 0,
        _recursions   = 0;
    _object       = NULL;
    _owner        = NULL;  // 指向持有ObjectMonitor对象的线程
    _WaitSet      = NULL;  // 处于wait状态的线程,会被加入到_WaitSet,调用了wait方法之后会进入这里
    _WaitSetLock  = 0 ;
    _Responsible  = NULL ;
    _succ         = NULL ;
    _cxq          = NULL ;
    FreeNext      = NULL ;
    _EntryList    = NULL ; // 没没有抢到锁,处于等待锁block状态的线程,会被加入到该列表
    _SpinFreq     = 0 ;
    _SpinClock    = 0 ;
    OwnerIsThread = 0 ;
}

同步代码块

我们通过借助javap命令查看synchronizedTest.clsss的字节码

public class synchronizedTest {
    static Object test=new Object();
    public static void main(String[] args) {
        synchronized (test){
            System.out.println("test synchronized");
        }
    }
}

找到这个类的class文件,在class文件目录下执行 javap -v synchronizedTest.class ,

反汇编效果如下:

可以看到,字节码是通过 monitorenter monitorexit 两个指令进行控制的。

monitorenter 是上锁开始的地方,monitorexit 是解锁的地方,其中被monitorenter和monitorexit包围住的指令就是上锁的代码 。

  1. monitorenter,当执行到monitorenter指令时,线程就会去尝试获取该对象对应的Monitor的所有权,即尝试获得该对象的锁。如果当前monitor的计数器为0时,线程就会获得monitor对象锁,并且计数器Count自增1,那么该线程就是monitor的拥有者(owner)。
  2. 如果该线程已经是monitor的拥有者,又重新进入,就会把计数器Count再次自增1。也就是可重入的。
  3. monitorexit,执行monitorexit的线程必须是monitor的拥有者,指令执行后,monitor的计数器Count减1,如果减1后计数器Count为0,则该线程会释放monitor对象锁。其他被阻塞的线程就可以尝试去获取monitor的所有权。
  4. 倘若其他线程已经拥有monitor 的所有权,那么当前线程获取monitor对象锁失败将被阻塞并进入到EntryList中,直到锁被释放为止。

monitorexit指令出现了两次,第1次为同步正常退出释放锁;第2次为发生异常退出释放锁

详细流程:

  • 加锁时,即遇到Synchronized关键字时,线程会先进入monitor的_EntryList队列阻塞等待。
  • 如果monitor的_owner为空,则从队列中移出并赋值与_owner。
  • 如果在程序里调用了wait()方法,则该线程进入_WaitSet队列。我们都知道wait方法会释放monitor锁,即将_owner赋值为null并进入_WaitSet队列阻塞等待。这时其他在_EntryList中的线程就可以获取锁了。
  • 当程序里其他线程调用了notify/notifyAll方法时,就会唤醒_WaitSet中的某个线程,这个线程就会再次尝试获取monitor锁。如果成功,则就会成为monitor的owner。
  • 当程序里遇到Synchronized关键字的作用范围结束时,就会将monitor的owner设为null,退出。


 

同步方法的底层实现

我们通过借助javap命令查看SyncTest.clsss的字节码

public class SyncTest {
    public static void main(String[] args) {
        hello();
    }
    public static synchronized void hello(){
        System.out.println("hello synchronized!");
    }
}

找到这个类的class文件,在class文件目录下执行 javap -v SyncTest.class ,

反汇编效果如下:

可以看到,在字节码的体现上,这里并没有monitorenter和moniterexit两条指令,而是只给方法加了一个 flag:ACC_SYNCHRONIZED ,这其实也容易理解,因为整个方法都是同步代码,因此就不需要标记同步代码的入口和出口。当线程线程执行到这个方法时会判断是否有ACC_SYNCHRONIZED标志,如果有的话则会尝试获取monitor对象锁。当该对象的 monitor 的计数器count为0时,那线程可以成功取得 monitor,并将计数器值设置为 1,取锁成功。当同步方法执行完后,该对象的 monitor 的计数器减1,计数器的值为0时,执行线程将释放 monitor(锁),其他线程才有机会持有 monitor。

总的来说,synchronized的底层原理是通过monitor对象来完成的,对于同步代码块是使用monitorenter和monitorexit指令来完成加锁和解锁。对于同步方法则是通过判断方法上是否有ACC_SYNCHRONIZED标志来尝试获取monitor对象锁。

方法级的同步是隐式的(同步方法)。同步方法的常量池中会有一个 ACC_SYNCHRONIZED 标志。当某个线程要访问某个方法的时候,会检查是否有 ACC_SYNCHRONIZED,如果有设置,则需要先获得监视器锁,然后开始执行方法,方法执行之后再释放监视器锁。这时如果其他线程来请求执行方法,会因为无法获得监视器锁而被阻断住。值得注意的是,如果在方法执行过程中,发生了异常,并且方法内部并没有处理该异常,那么在异常被抛到方法外面之前监视器锁会被自动释放。

同步代码块使用 monitorenter 和 monitorexit 两个指令实现。 可以把执行 monitorenter 指令理解为加锁,执行 monitorexit 理解为释放锁。 每个对象维护着一个记录着被锁次数的计数器。未被锁定的对象的该计数器为 0,当一个线程获得锁(执行 monitorenter )后,该计数器自增变为 1 ,当同一个线程再次获得该对象的锁的时候,计数器再次自增。当同一个线程释放锁(执行 monitorexit 指令)的时候,计数器再自减。当计数器为 0 的时候。锁将被释放,其他线程便可以获得锁。

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

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

相关文章

PLC远程调试-无需硬件设备-V3.2.0发布

文章目录 前言一、无缝连接,突破距离限制二、高效调试,提升工作效率三、安全可靠,保护数据安全四、用户友好,简化操作流程五、软件地址六、远程调试软件 七、基本操作1、订阅主题2、连接3、串口调试4、网口调试 八、软件地址结束语…

【数据结构精讲】01绪论(基本概念介绍和时间复杂度计算)

绪论 在绪论这部分会介绍常用的一些基本概念,让同学们对这门课的整体有所了解! 数据结构以及相关概念 一、几个重要概念 1、数据:凡是能输入到计算机并能被计算机程序处理的符号的总称 2、数据元素:数据的基本单位&#xff0…

C++实现单向链表操作(实验3--作业)

一、单向链表介绍 单向链表是一种常见的数据结构,它由一系列节点组成,每个节点包含一个数据域和一个指向下一个节点的指针域。 结构特点: 单向链表的节点通过指针链接在一起,形成一个线性的数据结构。链表的头节点通常是一个特殊…

【Google Chrome Windows 64 version及 WebDriver 版本】

最近升级到最新版本Chrome后发现页面居然显示错乱实在无语, 打算退回原来的版本, 又发现官方只提供最新的版本下载, 为了解决这个问题所有收集了Chrome历史版本的下载地址分享给大家. Google Chrome Windows version 64 位 VersionSize下载地址Date104.0.5112.10282.76 MBhtt…

leetcode 2398.预算内的最多机器人数目

2398.预算内的最多机器人数目 题意: 解析: 需要注意的是,题目询问中连续是子数组的意思,即求满足条件的最长子数组的长度。 因为是连续的,所以可以用双指针扫描整个数组。每次将右指针 r r r 向右移动一个位置&…

Vue的缓存组件 | 详解KeepAlive

引言 在Vue开发中,我们经常需要处理大量的组件渲染和销毁操作,这可能会影响应用的性能和用户体验。而Vue的KeepAlive组件提供了一种简便的方式来优化组件的渲染和销毁流程,通过缓存已经渲染的组件来提升应用的性能。 本文将详细介绍Vue的Ke…

2024年职场人士PDF转换的新宠儿

PDF文件已经成为日常收发文件的首选项了,它能保证文件页面内容、版式的统一性,但是不太好编辑,所以我们还需要一些小工具来帮我们解决这个问题。今天我们来探讨一下有什么可以从pdf转换器免费版官网下载的工具。 1.Foxit PDF转换大师 链接一…

BuripSuiteProfessional 抓取HTTPS配置

1.电脑拿开代理 谷歌为例 点击三点-设置 -输入代理--点击代理设置 打开手动代理---IP ,端口如图-点击保存 2.下载CA证书 打开代理后,谷歌浏览器打开,输入/burp--如下图-点击CA证书下载证书 选择下载目录--桌面 3.安装CA证书 谷歌浏览器中点开设置-输入证书-点击安全 点击…

Apollo(阿波罗)架构由浅入深剖析

1.最简架构 如果不考虑分布式微服务架构中的服务发现问题,Apollo 的最简架构如下图所示: 注意事项: ConfigService 是一个独立的微服务,服务于 Client 进行配置获取。 Client 和 ConfigService 保持长连接,通过一种拖拉结合 (push & pull) 的模式,实现配置实时更新…

【鸿蒙】HarmonyOS NEXT星河入门到实战8-自定义组件-组件通信

目录 1、模块化语法 1.1 模块化基本认知 1.2 默认导出和导入 1.2.1 在ets下新建tools目录 1.2.2 在tools下新建moduls.ets文件 1.2.3 index.ets 1.3 按需导出和导入 1.4 全部导入 2、自定义组件 -基础 2.1 自定义组件 - 基本使用 2.2 自定义组件 -通用样式 2.2.1 et…

Wophp靶场寻找漏洞练习

1.命令执行漏洞 打开网站划到最下,此处的输入框存在任意命令执行漏洞 输入命令whoami 2.SQL注入 搜索框存在SQL注入,类型为整数型 最终结果可以找到管理员账户和密码 3.任意文件上传漏洞 在进入管理员后台后,上传木马文件 访问该文件&…

【智路】智路OS airos-edge

欢迎来到智路OS https://gitee.com/ZhiluCommunity/airos-edge 智路OS是全球首个开源开放的智能网联路侧单元操作系统(简称“智路OS”), 是以高等级自动驾驶技术为牵引,沉淀出来的“车路云网图”一体化的智能交通基础软件平台。…

基于SpringBoot+Vue的智慧自习室预约管理系统

作者:计算机学姐 开发技术:SpringBoot、SSM、Vue、MySQL、JSP、ElementUI、Python、小程序等,“文末源码”。 专栏推荐:前后端分离项目源码、SpringBoot项目源码、SSM项目源码 系统展示 【2025最新】基于JavaSpringBootVueMySQL的…

基于vue框架的宠物店管理系统的设计与实现4czn0(程序+源码+数据库+调试部署+开发环境)系统界面在最后面。

系统程序文件列表 项目功能:用户,商品分类,服务类型,商品信息,商品订单,宠物服务,服务预约,服务评价,商品咨询 开题报告内容 基于Vue框架的宠物店管理系统的设计与实现开题报告 一、引言 随着宠物行业的蓬勃发展,宠物店作为宠物产品与服务的重要提供…

神经网络通俗理解学习笔记(2)循环神经网络RNN

循环神经网络RNN 序列建模自回归模型隐变量自回归模型 文本数据预处理循环神经网络随时间反向传播循环神经网络代码实现RNN的长期依赖问题 序列建模 序列数据-时间 不同时间上收集到的数据,描述现象随时间变化的情况 时间序列分析是指从时间排列的数据点中抽取有价…

Linux下编译Kratos

本文记录在Linux下编译Kratos的流程。 零、环境 操作系统Ubuntu 22.04.4 LTSVS Code1.92.1Git2.34.1GCC11.4.0CMake3.22.1Boost1.74.0oneAPI2024.2.1 一、依赖与代码 1.1 安装依赖 apt-get update apt-get install vim openssh-server openssh-client ssh \build-essential …

Web:HTTP包的相关操作

目录 一、请求包修改页面来源 二、Cookie身份认证 三、XXF修改本地访问 四、向页面同时发出GET和POST请求 一、请求包修改页面来源 题目提示要从 http://localhost:8080/flag3cad.php?a1,请求包中没有指定请求来源,需要指定。 而表示页面来源的字段…

cv2.bitwise_or 提取ROI区域

原图如下所示,想提取圆形ROI区域,红色框 img np.ones(ori_img.shape, dtype"uint8") img img * 255 cv2.circle(img, (50,50), 50, 0, -1) self.bitwiseOr cv2.bitwise_or(ori_img, circle)使用一个和原图尺寸一致的图像做mask,图白圆黑 以…

【Echarts】vue3打开echarts的正确方式

ECharts 是一个功能强大、灵活易用的数据可视化工具,适用于商业报表、数据分析、科研教育等多种场景。那么该如何优雅的使用Echarts呢? 这里以vue3为例。 安装echarts pnpm i echarts封装公用方法 // ts-nocheck import * as echarts from echarts; // 我们这里借…

Naive UI中的时间选择器如何禁止选择今天之前的时间

:is-date-disabled"dateDisabled"<n-date-pickerv-if"formData.timeEndActive"type"date"style"width: 100%"placeholder"请选择任务结束时间"value-format"yyyy-MM-dd 23:59:59":is-date-disabled"dateD…