synchronized 浅读解析 一

引言

在学习synchronized前,我们实际上是需要先了解Java对象在jvm中的储存结构,在了解了它的实际储存方式后,再对后边的锁学习会有一个更好更深入的理解。

一、对象结构

  1. 我们为什么要知道什么是对象头

    1. 在学习synchronized的时候,所涉及到的大部分原理都是与对象头相关,所以理解对象头是我们深入理解synchronized的前提条件。

  2. 什么是对象头

    我们编写一个Java类,编译后会生成.class文件,当类加载器将class文件加载到jvm时,会生成一个Klass类型的对象(c++),称为类描述元数据,存储在方法区中,即jdk1.8之后的元数据区。当使用new创建对象时,就是根据类描述元数据Klass创建的对象oop,存储在堆中。每个java对象都有相同的组成部分,称为对象头。

  3. 如何查看对象头,jol-core 查看对象头的神器(Java对象布局)

    <dependency><groupId>org.openjdk.jol</groupId><artifactId>jol-core</artifactId><version>0.16</version>
    </dependency>
  4. 查看某个对象的布局

    public class User {
    ​private String name;private Integer age;
    ​public User(String name, Integer age) {this.name = name;this.age = age;}
    ​public String getName() {return name;}
    ​public void setName(String name) {this.name = name;}
    ​public Integer getAge() {return age;}
    ​public void setAge(Integer age) {this.age = age;}
    }
    ​
    User a = new User("小明", 12);
    System.out.println(ClassLayout.parseInstance(a).toPrintable());

    输出:

    代表含义:

    OFF:偏移量,通常指的是指定再数据结构中的某个元素的位置。它表示从起始位置偏移多少个字节或者多少个元素才能到达目标元素所在的位置。例如上图中的object header,mark占了8字节,由0开始,class就要在8的位置开始查找位置。

    SZ: 代表大小,所占空间大小,字节单位

    TYPE DESCRIPTION:类型描述,object header 为对象头

    VALUE: 当前内存中存储的值。

  5. object header中对象当前的状态

    1. 当无锁状态下

      • 前25位bit位是未使用的,hashcode占了31bit,中间的1bit位是未使用的,4bit位是分代年龄,最后的3bit位是 001 表示无锁状态

    2. 如果是偏向锁的状态

      1. 前54bit位是当前线程指针,2bit位是Epoch,1bit位未使用,4bit位分代年龄,剩余3bit位分别是,101 表示锁定状态

    3. 如果是轻量锁状态

      1. 前62位指向lock record指针 最后的锁类型为 00

    4. 如果是重量锁

      1. 前62位指向互斥锁指针,锁类型位10

    5. GC标记

      1. 前62位为空,锁类型为11

  6. 在HotSpot虚拟机中,对象在内存中存储的布局可以分为三块区域:对象头、实例数据和对齐填充

    1. 对象头占 4字节,共64bit位,在jvm源码中对应mark-word 对象,主要用于存储一些列的标记位,比如哈希值、轻量级锁的标记位、偏向锁标记位、分代年龄

    2. Klass Pointer:Class对象的类型指针,Jdk1.8默认开启指针压缩后为4字节,关闭指针压缩后长度为8字节。其指向的位置是对象对应的Class对象的内存地址。

    3. 对象实际数据:包括对象的所有成员变量,大小由各个成员变量决定

    4. 对齐: 这段对齐是非必须的,由于HotSpot虚拟机的内存管理系统要求对象起始地址必须是8字节的整数倍,如果实例数据对象没有对齐的划,就需要对齐占位来补全。

  7. 在mark-word锁类型标记中,无锁、偏向锁、轻量锁、重量锁、以及GC标记,五种类中没法用2比特位标记,所以无锁、偏向锁又往前占了以为偏向锁标记,最终:001为无锁、101为偏向锁。

二、synchronized

  1. 关于synchronized的锁优化升级膨胀最终修改的就是对象头中最后三位的标识来区分不同的锁类型,从而采取不同的策略来提升性能。

    为什么第二个对象里的HashCode展示和 下方列出的不一样

    1. 倒过来是因为大小端存储导致:

      • Big-Endian:高位字节存放于内存的低地址端,低位字节存放于内存的高地址端

      • Little-Endian:低位字节存放于内存的低地址端,高位字节存放于内存的高地址端

  2. Monitor对象

    概念:在HotSpot虚拟机中,monitor是由C++中ObjectMonitor实现。synchronized的运行机制,就是当JVM检测到对象在不同的竞争状态时,会自动切换到适合的锁实现,这种切换就是锁的升级、降级。那么三种不同的Monitor实现,也就是常说的三种不同的锁:偏向锁、轻量级锁和重量级锁。当一个Monitor被某个线程持有后,它便处于锁定状态。

    1. 源码地址

      ObjectMonitor() {_header       = NULL;_count        = 0; // 记录个数_waiters      = 0, _recursions   = 0; // 线程重入次数_object       = NULL; // 存储monitor对象_owner        = NULL; // 持有当前线程的owner_WaitSet      = NULL; // 处于wait状态的线程,会被假如到_WaitSet_WaitSetLock  = 0 ;_Responsible  = NULL ;_succ         = NULL ;_cxq          = NULL ; // 单向列表FreeNext      = NULL ;_EntryList    = NULL ; // 处于等待锁block状态的线程,会被加入到该列表_SpinFreq     = 0 ;_SpinClock    = 0 ;OwnerIsThread = 0 ;_previous_owner_tid = 0;}
    2. 每个 Java 对象头中都包括 Monitor 对象(存储的指针的指向),synchronized 也就是通过着一种方式获取锁,也就解释了synchronized() 括号里放任何对象都能获得锁。

2.1 synchronized特性
  1. 原子性

    原子性是指一个操作不可中断的,要么全部执行成功,要么全部执行失败。

    synchronized可以保证统一时间只有一个线程能拿到锁,进入到代码块执行。

  2. 代码:

    public static void main(String[] args) throws InterruptedException {for (int i = 0; i < 10; i++) {Thread thread = new Thread(() -> {for (int i1 = 0; i1 < 10000; i1++) {add();}});thread.start();}Thread.sleep(1000);System.out.println(counter);
    }
    public static void add() {synchronized (AtomicityTest.class) {counter++;}
    }

  3. 反编译:

    可以看到add指令里的monitorenter就是表示先去获取对象的monitor,monitorexit就表示使用结束退出,由其他线程开始竞争锁。

    指令逻辑:

    1. 每个monitor维护着一个记录着拥有次数的计数器。未被拥有的monitor的该计数器为0,当一个线程获得monitor(执行monitorenter)后,该计数器自增变为 1 。

      • 当同一个线程再次获得该monitor的时候,计数器再次自增;

      • 当不同线程想要获得该monitor的时候,就会被阻塞。

    2. 当同一个线程释放 monitor(执行monitorexit指令)的时候,计数器再自减。当计数器为0的时候。monitor将被释放,其他线程便可以获得monitor。

  4. 可见性

    通过synchronized也能保持可见性。

    和volatile的区别在于,volatile是通过内存屏障来实现的可见性,而synchronized实现

    1. 线程解锁前必须把共享变量的最新值刷新到主内存中

    2. 加锁前必须清空工作内存中共享变量的值,从而使用共享变量事需要从主内存中重新读取最新的值。

    3. synchronized靠系统内核互斥锁实现。退出代码块时刷新变量到主内存。

  5. 有序性

    as-if-serial,保证不管编译器和处理器为了性能优化会如何进行指令重排序, 都需要保证单线程下的运行结果的正确性。也就是常说的:如果在本线程内观察,所有的操作都是有序的;如果在一个线程观察另一个线程,所有的操作都是无序的。

    也就是说,在使用了synchronized后,会发生指令重排的可能,比如设计模式中的单例模式,假如仅是做了双重判空和synchronized加锁,可能会导致多线程获取实例不同。比如,单例模式中的双重检查锁,增加volatile的目的就是为了防止指令重排,以防多线程的情况下,A线程执行到了指向内存地址的步骤,B线程就获取到了锁从而导致new出了多个对象的问题,失去了单例模式的本意。

    public class Singleton {private Singleton() {};private volatile static Singleton instance;public Singleton getInstance() {if (instance == null) {synchronized (Singleton.class) {if (instance == null) {// B线程进入判断,此时的instance还是为空的instance = new Singleton(); // 执行new之后代码块结束,monitorexit指令退出,下一个线程已经可以获取到锁,开始指向内存地址。}}}return instance;}
    }
  6. 可重入性

    synchronized 是可重入锁,也就是说,允许一个线程二次请求自己持有对象锁的临界资源,这种情况称为可重入锁。

    为什么需要可重入锁,就是因为for循环调用时可能会发生当前线程循环请求已经拥有的锁,实现逻辑就是在monitor里存了一个计数器,每当当前线程获取到一次计数器就+1,退出一次就-1直到清零释放锁。

2.2 锁升级过程
  1. 锁的四种状态:无锁、偏向锁、轻量级锁、重量级锁。这四种锁只能随着顺序升级,不能降级。

  2. 关于每个锁的状态的存储结构可以看synchronized浅读解析一里的object header部分。

  3. 锁的概念:

    • 无锁

      • 无锁是指没有对资源进行锁定,所有线程都能访问并修改同一个资源。但同时只有一个线程能修改成功。

    • 偏向锁

      • 初次执行到synchronized代码块的时候,锁对象会编程偏向锁,字面的意思就是偏向第一个获得它的线程的锁。

      • 指的就是当同一段同步代码一直被同一个线程所访问时,既不存在多个线程的竞争时,那么这个线程在后续访问时会自动获得锁,从而降低锁带来的消耗,既提高性能。

      • 没有线程之间的切换消耗,所以性能极高。

    • 轻量锁(自旋锁)

      • 大概概念就是当同步代码块都处于无锁状态,但同时有两个线程都在争夺锁,线程1争取成功了,线程2就会失败进入自旋状态。为避免同步代码块执行时间短,可以快速切换到另一个线程的情况,线程2会进入一段时间的自旋状态,既一段无意义的for循环。假如for循环10次后还是获取不到锁,就会升级到下一个状态。

    • 重量级锁

      • 当自旋结束后还未获取到锁,就会膨胀为重量级锁,阻塞住线程。重量级锁十分消耗性能。

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

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

相关文章

Java 集合、迭代器

Java 集合框架主要包括两种类型的容器&#xff0c;一种是集合&#xff08;Collection&#xff09;&#xff0c;存储一个元素集合&#xff0c;另一种是图&#xff08;Map&#xff09;&#xff0c;存储键/值对映射。Collection 接口又有 3 种子类型&#xff0c;List、Set 和 Queu…

P1028 [NOIP2001 普及组] 数的计算题解

题目 给出正整数n&#xff0c;要求按如下方式构造数列&#xff1a; 只有一个数字n的数列是一个合法的数列。在一个合法的数列的末尾加入一个正整数&#xff0c;但是这个正整数不能超过该数列最后一项的一半&#xff0c;可以得到一个新的合法数列。 请你求出&#xff0c;一共…

vtk三维场景基本要素 灯光、相机、颜色、纹理映射 简介

整理一下VTK 三维场景基本要素&#xff0c;后面会一一进行整理&#xff1b; 1. 灯光 vtkLight 剧场里有各式各样的灯光&#xff0c;三维渲染场景中也一样&#xff0c;可以有多个灯光存在。灯光和相机 是三维渲染场景必备的要素&#xff0c;vtkRenderer会自动创建默认的灯光和…

24个已知403绕过方法的利用脚本

介绍 一个简单的脚本&#xff0c;仅供自用&#xff0c;用于绕过 403 在curl的帮助下使用24个已知的403绕过方法 它还可用于比较各种条件下的响应&#xff0c;如下图所示 用法 ./bypass-403.sh https://example.com admin ./bypass-403.sh website-here path-here 安装 git …

opencv图像像素的读写操作

void QuickDemo::pixel_visit_demo(Mat & image) {int w image.cols;//宽度int h image.rows;//高度int dims image.channels();//通道数 图像为灰度dims等于一 图像为彩色时dims等于三 for (int row 0; row < h; row) {for (int col 0; col < w; col) {if…

[ai笔记1] 借着“ai春晚”开个场

1 文思ai笔记-新的开始 今天是2024年2月29日&#xff0c;也是传统农历的除夕夜。早起在ai圈看到一个比较新奇的消息&#xff0c;ai春晚今日举办&#xff0c;竟然有一点小小的激动。这些年确实好久没看过春晚了&#xff0c;自己对于春晚的映像还停留在“白云黑土”、“今天&…

AI修复历史人物 图像转真人 绝密档案

修复李白 开启control 不要点爆炸小按钮 权重建议&#xff1a;0.7-1.2 采样&#xff1a;DPM SDE Karras 如果人眼不好&#xff0c;开启高清修复&#xff0c;进行2次尝试 高难度 修复张居正 softhed 1 lineart_真实 1 适当调整lineart进入值。 如果效果不好&#xff…

ubuntu中尝试安装ros2

首先&#xff0c;ubuntu打开后有个机器人栏目&#xff0c;打开后&#xff0c;有好多可选的&#xff0c;看了半天 ,好像是博客&#xff0c;算了&#xff0c;没啥关系&#xff0c;再看看其他菜单 这些都不是下载链接。先不管&#xff0c;考虑了一下&#xff0c;问了ai&#xff…

一、西瓜书——绪论

第一章 绪论 1.独立同分布 通常 假设 样本空间 中 全 体样 本 服 从 一 个 未 知 “ 分 布 ” ( d i s t r i b u t i o n ) D , 我们获得的每个样本都是独立地从这个分布上采样获得的&#xff0c; 即 “ 独 立同 分布 ” ( i n d e p e n d e n t a n d i d e n t ic a …

【2024.02.11】定时执行专家 V6.9 龙年春节版 - 下载地址更新日志

目录 ◆ 最新版下载链接 ◆ 软件更新日志 – TimingExecutor Full Change Log ▼2024-02-11 V6.9 ▼2023-06-16 V6.8.2 ▼2023-02-27 V6.7 ▼ 2023-01-23 V6.6 ▼ 2023-01-20 V6.5 ▼ 2022-12-25 V6.4 ▼ 2022-11-15 V6.3 ▼ 2022-10-01 V6.2 ▼ 2022-07-…

寒假作业-day8

代码&#xff1a; #include<stdio.h> #include<stdlib.h> #include<string.h>int jiecheng(int n){if(n<1)return 1; return n*jiecheng(n-1); }int sum(int n){if(n<0)return 0;return nsum(n-1); }int feb(int n){if(n<2)return 1;elsereturn fe…

Java图形化界面编程—— LayoutManager布局管理器笔记

2.4 LayoutManager布局管理器 之前&#xff0c;我们介绍了Component中有一个方法 setBounds() 可以设置当前容器的位置和大小&#xff0c;但是我们需要明确一件事&#xff0c;如果我们手动的为组件设置位置和大小的话&#xff0c;就会造成程序的不通用性&#xff0c;例如&…

跟踪分析一款新型Megahorse窃密木马

前言 最近几年黑客组织利用各种不同类型的恶意软件进行的网络犯罪活动越来越多&#xff0c;这些恶意软件包含勒索病毒、挖矿病毒、APT远控后门、银行木马、僵尸网络等&#xff0c;企业的数据一直是企业的核心资产&#xff0c;勒索攻击也由最初始的单纯的通过某个单一漏洞传播勒…

四.Linux实用操作 12-14.环境变量文件的上传和下载压缩和解压

目录 四.Linux实用操作 12.环境变量 环境变量 环境变量--PATH $ 符号 自行设置环境变量 自定义环境变量PATH 总结 四.Linux实用操作 13.文件的上传和下载 上传&#xff0c;下载 rz&#xff0c;sz命令 四.Linux实用操作 14.压缩和解压 压缩格式 tar命令 tar命令压缩…

uniapp微信小程序开发踩坑日记:Pinia持久化

如果你使用过Pinia&#xff0c;那你应该知道Pinia持久化插件&#xff1a;https://prazdevs.github.io/pinia-plugin-persistedstate/zh/ 但由于官方文档提供的说明并不是针对小程序开发&#xff0c;所以我们在使用这个插件实现uniapp小程序开发中Pinia持久化会出现问题 我在C…

Redis.conf 配置文件解读

1、单位 容量单位不区分大小写&#xff0c;G和GB没有区别 配置文件 unit单位 对大小写不敏感 2、组合配置 可以使用 include 组合多个配置问题 3、网络配置 bind 127.0.0.1 # 绑定的ip protected-mode yes # 保护模式 port 6379 # 端口设置4、通用 GENERAL daemoniz…

Arm发布新的人工智能Cortex-M处理器

Arm发布了一款新的Cortex-M处理器&#xff0c;旨在为资源受限的物联网&#xff08;IoT&#xff09;设备提供先进的人工智能功能。这款新的Cortex-M52声称是最小的、面积和成本效率最高的处理器&#xff0c;采用了Arm Helium技术&#xff0c;使开发者能够在单一工具链上使用简化…

吉他学习:C大调第一把位音阶,四四拍曲目练习 小星星,练习的目的

第十三课 C大调第一把位音阶https://m.lizhiweike.com/lecture2/29364198 第十四课 四四拍曲目练习 小星星https://m.lizhiweike.com/lecture2/29364131 C大调第一把位音阶非常重要,可以多练习&#x

华为云ModelBox实战:体感小游戏应用实操

目录 一、VsCode插件注册ModelBox设备二、Windows SDK安装1.安装Git for Windows2.下载ModelBox SDK3.相关插件安装 三、体感小游戏应用开发1.技能模板使用2.AI应用示例3.体感小游戏体验 参与华为云活动【HCSD】ModelBox实战营邀请活动&#xff0c;呼朋唤友学AIoT&#xff0c;完…

《统计学简易速速上手小册》第9章:统计学在现代科技中的应用(2024 最新版)

文章目录 9.1 统计学与大数据9.1.1 基础知识9.1.2 主要案例&#xff1a;社交媒体情感分析9.1.3 拓展案例 1&#xff1a;电商销售预测9.1.4 拓展案例 2&#xff1a;实时交通流量分析 9.2 统计学在机器学习和人工智能中的应用9.2.1 基础知识9.2.2 主要案例&#xff1a;预测客户流…