【Java 并发编程】(二) 从对象内存布局开始聊 synchronized

news2024/9/24 11:29:26

对象的内存布局

首先抛出一个经典面试题: 一个 Object 对象占多大?
在这里插入图片描述

这里我用工具打印了出来, 发现是 “16bytes”, 也就是 16B; 为什么? 请继续往下看;

在这里插入图片描述

普通对象(除了数组), 由markword, 类型指针, 实例数据(就是对象里的成员), 对齐填充(整个对象大小要能被8B整数, 方便64bit总线)构成;

  • markword 中保存了监视器锁的信息, GC 的信息, 还可能保存默认的 hashCode 方法计算出的哈希值;

  • 类型指针指向这个对象所属的类的类对象;

  • java命令默认带两个压缩指针的参数, UseCompressedClassPointersUseCompressedOops(Ordinary Object Pointers), 在64位环境下, 一个指针应当占8字节; 第一个指令会将对象头中的类型指针压缩位4B, 第二个参数会将成员指针压缩为4B;

  • 一个java应用所占的内存大小, 几乎不会大到需要用 64 位的寻址空间, 32 位完全够了, 这是为什么可以开启指针压缩;

  • 对于Object类, markword 8B, 类型指针4B, Object类型没有实例数据0B, 对齐填充4B, 总共 16B

对象头

在这里插入图片描述

  • markword中记录了锁信息, GC信息(例如当前熬过了几次垃圾回收, 注意这个字段只有4bit最大值15)
  • 如果使用了默认的hashcode方法, 那么计算出来的 hash 值会被放到markword里保存; 而这个保存的地方, 和偏向锁是有冲突的, 如果调用过默认的 hashCode 方法, 偏向锁就不能用了;
  • 使用了偏向锁, 再调用默认的 hashCode, 会触发锁升级;

监视器锁

特点

  1. 对象级别的锁: 监视器锁是与对象关联的,每个对象都有一个内置的监视器锁。当一个线程获取了对象的监视器锁后,其他线程就无法同时获取该对象的监视器锁,直到锁被释放。

  2. 排他性访问: 监视器锁实现了排他性访问,即同一时刻只有一个线程能够持有对象的监视器锁,其他线程需要等待。

  3. 基于进入和退出监视器锁的规则: 线程可以进入同步代码块或同步方法,获取对象的监视器锁;当线程退出同步代码块或同步方法时,会释放对象的监视器锁。

  4. 可重入性: 监视器锁支持线程的可重入性,即同一个线程在持有锁的情况下可以重复地继续获取锁,而不会发生死锁。但是要注意获取多少次锁就需要释放多少次锁

  5. 等待和唤醒机制: 一个线程持有监视器锁时, 其它线程尝试获取锁时将被阻塞, 放到该锁的阻塞队列中; 持有锁的线程释放锁后, 如果有其他线程正在等待获取同一个对象的监视器锁,那么其中的一个线程会被唤醒;

Synchronized

synchronized代码块

synchronized(对象){
    ...
}
  • 使用同步代码块的线程执行时如果出现异常, 会自动释放锁;
  • 小括号内的对象又称互斥条件, 是同步代码块锁住的对象; 可以使用this, 某个特定类的对象, 某个类的类对象;
  • 当线程进入 synchronized 代码块时,它会尝试获取对象的监视器锁。如果对象的监视器锁已经被其他线程持有,则当前线程会被阻塞,直到获取到锁为止。获取到锁后,线程就可以执行 synchronized 代码块中的代码,直到代码块执行完毕或者抛出异常,然后释放锁。
  • 具有可重入的特点;

synchronized方法

  • synchronized关键字加在静态方法上, 则进入方法时会尝试获取该静态方法所属的类的类对象的监视器锁;
  • synchronized加在成员方法上, 进入方法时会尝试获取调用该方法的对象的的监视器锁;
  • 退出方法时释放锁;

synchronized实现原理

  1. 源码中, synchronized关键字
  2. 编译后变为对监视器锁的操作, monitor enter 和 monitor exit, 实际就是对对象头中markword的修改
  3. 在JVM执行过程中, 会自动进行锁升级
  4. 最终依靠汇编指令, lock_cmpxchg指令实现

非公平

  1. synchronized是非公平锁, 当一个线程尝试获取锁时, 不会优先让阻塞队列中的线程获取锁; 而是一上来自己就尝试获取锁;
  2. 通常是唤醒队头元素, 通常也就是等待时间最久的元素

wait / notify

  • 需要在synchronized代码块内部或者synchronized方法内部, 已经获取了某个对象的监视器锁的前提下, 才能用这个对象去调用wait/notify方法

  • wait:

    • 调用之前必须先获取到该对象的监视器锁
    • 某个对象调用wait方法后, 当前线程会释放这个对象的监视器锁, 并进入 WAITING 状态, 加入到该监视器锁的waiting队列中, 等待其他线程调用相同对象的notify/ notifyAll方法唤醒;
  • notify:

    • 调用之前必须先获取到该对象的监视器锁
    • 调用notify方法后会在该监视器锁的waiting队列中唤醒一个线程;
    • 被唤醒的线程会重新尝试获取该对象的监视器锁; 获取到以后才能继续执行; 获取失败就blocked
  • notifyAll:

    • 与notify的区别是会唤醒监视器锁waiting队列中的所有线程

为什么要在获取监视器锁以后才能 wait 和 notify ? 主要是为了防止一次 notify 调用导致多个 WAITING 状态的线程被唤醒; 在 synchronized 代码块内部, 唤醒时需要重新获取监视器锁, 避免多个线程能同时往下执行;

锁升级

  • synchronized 获取对象的锁时, 获取方式会根据线程竞争情况进行升级; 因为重量级锁是要向操作系统申请的, 时间消耗比较大;

  • 在Java中,锁的状态包括偏向锁、轻量级锁和重量级锁。

  • 偏向锁: 当一个线程第一次进入同步块时,会尝试获取对象的偏向锁, 本质就是通过 CAS(下文会有详细介绍) 的方式, 将指向本线程的指针, 写到对象头里; 以后相同线程再尝试获取相同对象的监视器锁时, 会直接获取成功;

  • 轻量级锁: 当有不同线程尝试获取锁时, 锁状态升级为轻量级锁, 轻量级锁使用CAS操作来尝试获取锁。和获取偏向锁类似, 用CAS操作尝试将自己的 LockRecord 指针写入对象头来实现获取锁; 每个线程有自己的方法栈, 方法栈里有LockRecord; 如果获取失败,线程就会进行自旋操作继续尝试, 自旋达到一定次数后(由JVM动态计算此阈值),轻量级锁会升级为重量级锁;

    为什么变成用 LockRecord了? 接着用线程指针不行吗? 应该需要保存一些额外的信息了; 比如说自旋次数;

  • 重量级锁: 将获取锁失败的线程阻塞;

    重量级锁是向操作系统申请获取的, 重量级锁的操作都是由操作系统负责的;

    当某个线程获取重量级锁失败时, 会进入该锁对应的阻塞队列中;

    当临界区大, 并发级别高时, 适合使用重量级锁, 避免cpu资源的消耗;临界区小, 并发级别低时, 适合使用轻量级锁

  • 补充两个概念, 知道就行

    锁粗化

    JVM检测到一连串的操作都是对同一个对象进行加锁的话, 会将锁的范围粗化到一系列操作的外部, 只进行一次加锁;

    StringBuffer是线程安全的, append方法需要加锁;

    while(i < 100){
    i++;
    sb.append("hello");
    }
    

    锁消除

    当确定某个锁不必要的时候, 例如一个方法中有StringBuffer类型的局部变量, 其它线程不可能访问到这个局部变量, 那么会自动消除StringBuffer内的加锁操作

在Java中,监视器锁(也称为内置锁或对象锁)在实现上通常包含两种队列:

  1. 等待队列(Wait Set):等待队列用于存放那些因为调用了 Object.wait()Thread.join() 或者 LockSupport.park() 等方法而进入等待状态的线程。当线程被唤醒时,它会从等待队列中移出,重新进入锁的竞争中。
  2. 阻塞队列(Blocked Set):阻塞队列用于存放那些尝试获取锁但由于锁被其他线程持有而进入阻塞状态的线程。这些线程会被放入锁的阻塞队列中等待锁的释放。

CAS

  • CAS(Compare and Swap)是一种原子性操作, 能够在无锁的情况下实现安全的值更新操作;

  • 也可以宽泛一点就说成是自旋

  • 它的基本思想是,要修改某个值时, 先读取当前值, 记为e, 计算出要修改为的值v, 写回之前再次读取当前值n, 如果n和e不相同, 说明修改失败, 放弃修改, 重新执行此过程

  • 多核环境下, 底层是由 lock_cmpxchg 汇编指令实现的, cmpxchg这条指令并没有原子性, 必须是lock_cmpxchg指令, 前面的lock指令保证了原子性;

  • 例如由CAS + 自旋机制 实现轻量级锁机制

    1. 线程尝试获取锁时,首先会通过CAS操作尝试获取轻量级锁
    2. 如果获取锁失败, 通过自旋操作不断尝试获取锁
    while(!cas){
    count++;
    if(cnt > threshold)
    升级重量级锁;
    }
    
  • ABA问题

    当我写回之前再次读取当前值n, 即使仍然等于e, 我也不知道到底是没被人改过, 还是改过只不过最后还是改成了e;

    解决: 给值添加版本号, 读取值的时候连同版本号一起读取, 如果值相同但是版本号不同, 说明已经被修改过;

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

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

相关文章

谷歌前CEO施密特放飞自我:斯坦福课堂上的AI洞见

谷歌前CEO施密特放飞自我&#xff1a;斯坦福课堂上的AI洞见 曾经担任谷歌CEO长达10年之久的Eric Schmidt&#xff0c;近日在斯坦福大学计算机学院的会议上发表了一场引人深思的演讲。在这场讲座中&#xff0c;他全程“放飞自我”&#xff0c;甚至在讲话中提醒台下学生&#xf…

将 PDF 转换为 JPG 的 3 种简便方法

PDF&#xff08;Portable Document Format&#xff09;是Adobe公司开发的一种用于呈现文档的常用文件格式。PDF文件可以包含图像和文本。它承载着固定布局平面文档的完整描述&#xff0c;包括文本、字体、图像等信息。但很多时候&#xff0c;你需要将PDF转换为JPG。 您想将PDF…

The Science of Procrastination - And How To Manage It

img&#xff1a;Perseid Meteors over Stonehenge 一场英仙座流星雨 虽然英仙座流星雨在昨晚达到了顶峰&#xff0c;但一些英仙座流星雨在接下来的几个晚上仍然可以看到 Lets face it. Youre likely reading this article in an effort to avoid some other tasks youre pro…

UART通信实现与验证(RS485)

前言 UART是一种常用的串行通信协议&#xff0c;RS485则是一种用于长距离和抗干扰的物理层标准。结合UART和RS485可以实现可靠的数据传输&#xff0c;特别是在多点通信和长距离应用中。通过合适的硬件连接、软件配置和验证测试&#xff0c;可以确保这一通信系统的稳定性和数据完…

达美航空运营中断造成重大财务损失

达美航空遇运营中断 达美航空公司&#xff08;Delta Air Lines&#xff0c;股票代码&#xff1a;DAL&#xff09;周四宣布&#xff0c;由于CrowdStrike引发的系统故障&#xff0c;其运营受到了严重影响。本季度&#xff0c;该公司预计收入将减少3.8亿美元。这次故障导致达美航…

stm32入门学习14-电源控制

有时候我们的程序中有些触发执行条件&#xff0c;有时这些触发频率很少&#xff0c;我们的程序就一直在循环&#xff0c;这样就很浪费电&#xff0c;我们可以通过PWR电源控制来实现低功耗模式&#xff0c;即只有在触发时才执行程序&#xff0c;其余时间可以关闭一些没必要的设备…

zdppy+vue3+onlyoffice文档管理系统项目实战 20240812上课笔记

遗留问题 1、增加新建和导入按钮&#xff0c;有按钮了&#xff0c;但是还没有完善&#xff0c;图标还不对&#xff0c;需要解决 2、登录功能 3、用户管理 4、角色管理 5、权限管理 6、分享功能 解决新建和导入的图标问题 解决代码&#xff1a; <a-button type"prim…

数据中台之数据开发-算法开发

目录 一、数据智能化挑战 二、算法开发的作用 三、算法架构与算法使用场景 3.1 算法架构总览 3.2 算法的适用场景 3.2.1 金融风控和反欺诈 3.2.2 文本挖掘分析 3.2.3 广告精准营销 3.2.4 个性化推荐 四、 算法开发涉及的内容 4.1 建模 4.1.1 可视化建模 4.1.1.1 可…

Vue3使用el-table实现多级表头合并列

不难发现&#xff0c;需要多级表头的列只需要在外面包一层el-table-column起名字即可 <el-table :data"tableData" style"width: 100%"><el-table-column type"index" label"序号" width"100" align"center&q…

Operator

国内operator学习大全 simple example 一、实践 二、理论问答 1.这张图属于 client-go 的 Informer 框架 配置和 Restclient或者 ClientSet 准备好后就可以通过客户端 CRUD k8s集群里面的资源 reflector 就是 watch和list k8s api 。就是监控资源变化和列出资源 ResourceVers…

最新图像修复论文汇总(2024年以来)(三)

汇总了自2024年以来新提出的高质量图像修复工作&#xff0c;包含扩散模型、transformer、mamba、sam等最前沿的技术&#xff0c;其中一些是ICLR、ICML、CVPR、ECCV、ACM MM 2024年的新作。 这里是第三部分&#xff0c;还有两部分请参阅。 最新图像修复论文汇总&#xff08;20…

python处理时间,按照周分割时间段

在实际的开发中&#xff0c;我们经常要设计一些工具类&#xff0c;对于时间来说&#xff0c;有时候需要将其处理成时间段。 例如&#xff0c;对于2024年08月01日到2024年08月16日的时间段&#xff0c;我们如何将其处理成时间段[2024-08-01, 2024-08-03], [2024-08-04, 2024-08-…

嘉立创EDA个人学习笔记1(PCB板介绍)

前言 本篇文章属于嘉立创EDA的学习笔记&#xff0c;来源于B站教学视频。下面是这位up主的视频链接。本文为个人学习笔记&#xff0c;只能做参考&#xff0c;细节方面建议观看视频&#xff0c;肯定受益匪浅。 嘉立创EDA-PCB设计零基础入门课程&#xff08;54集全&#xff09;_…

安卓应用开发学习:手机摇一摇功能应用尝试--摇骰子和摇红包

一、引言 前几天&#xff0c;我发布的日志《安卓应用开发学习&#xff1a;查看手机传感器信息》记录了如何查看手机传感器的信息&#xff0c;通过上述的方法&#xff0c;可以看到我的OPPO手机支持19种传感器。本篇日志就记录一下常见的加速度传感器的典型应用——“摇一摇”功…

ESP32添加组件02

1.接ESP32创建工程01&#xff0c;快捷键CtrlShiftp 2.输入组件的名称&#xff0c;然后按Enter键 3.创建结果如下图所示 目录结构如下 一级 component --文件夹 二级 led ---文件夹 led.c c文件 CMakeList.txt txt文件 三级 include 文件夹 led.h c头文件 4.主要代码 //led.c…

机器学习之 K 近邻算法图像识别实战

引言 在机器学习领域&#xff0c;K 近邻算法&#xff08;K-Nearest Neighbors, KNN&#xff09;是一种基于实例的学习方法&#xff0c;它可以根据样本之间的距离来进行分类或回归。本文将介绍如何使用 KNN 算法进行手写数字识别&#xff0c;并通过一个实际的例子来演示整个过程…

GPU纹理压缩格式(详解ETC1)

参考 纹理压缩格式原理概述&#xff1a; 你所需要了解的几种纹理压缩格式原理Compressed GPU texture formats – a review and compute shader decoders – part 1 ETC1格式&#xff1a;ETC1压缩纹理格式详解 ETC1 将4x4像素&#xff08;16RGB8位共384位&#xff0c;48字…

ChatGPT下的Java代码审计

吉祥知识星球http://mp.weixin.qq.com/s?__bizMzkwNjY1Mzc0Nw&mid2247485367&idx1&sn837891059c360ad60db7e9ac980a3321&chksmc0e47eebf793f7fdb8fcd7eed8ce29160cf79ba303b59858ba3a6660c6dac536774afb2a6330#rd 《网安面试指南》http://mp.weixin.qq.com/s?…

爬虫:scrapy基本使用:链家实例

scrapy基本使用 Scrapy是用纯Python实现一个为了爬取网站数据、提取结构性数据而编写的应用框架&#xff0c;用途非常广泛。 框架的力量&#xff0c;用户只需要定制开发几个模块就可以轻松的实现一个爬虫&#xff0c;用来抓取网页内容以及各种图片&#xff0c;非常之方便。 安…

【原创】java+swing+mysql的KTV管理系统设计与实现

个人主页&#xff1a;程序员杨工 个人简介&#xff1a;从事软件开发多年&#xff0c;前后端均有涉猎&#xff0c;具有丰富的开发经验 博客内容&#xff1a;全栈开发&#xff0c;分享Java、Python、Php、小程序、前后端、数据库经验和实战 开发背景&#xff1a; 随着社会的发展…