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://www.mzph.cn/news/672180.shtml

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

相关文章

破除Github API接口的访问次数限制

破除Github API接口的访问次数限制 1、Github介绍2、Github API接口2.1 介绍2.2 使用方法 3、Github API访问限制3.1 访问限制原因3.2 访问限制类别 4、Github API访问限制破除4.1 限制破除原理4.2 限制破除示例 1、Github介绍 Github&#xff0c;是一个面向开源及私有软件项目…

提升你的PHP开发效率:探索JetBrains PhpStorm 2022的全新特性

在当今快速发展的软件开发领域&#xff0c;选择一个强大且高效的开发工具对于提升开发效率、保证代码质量至关重要。对于PHP开发者来说&#xff0c;JetBrains PhpStorm一直是市场上最受欢迎的IDE之一。随着JetBrains PhpStorm 2022的发布&#xff0c;这款工具带来了一系列创新功…

MybatisPlus快速入门及常见设置

目录 一、快速入门 1.1 准备数据 1.2 创建SpringBoot工程 1.3 使用MP 1.4 获取Mapper进行测试 二、常用设置 2.1 设置表映射规则 2.1.1 单独设置 2.1.2 全局设置 2.2 设置主键生成策略 2.2.1 为什么会有雪花算法&#xff1f; 2.2.2 垂直分表 2.2.3 水平分表 2.…

JavaScript流程控制详解之顺序结构和选择结构

流程控制 流程控制&#xff0c;指的是控制程序按照怎样的顺序执行 在JavaScript中&#xff0c;共有3种流程控制方式 顺序结构选择结构循环结构 顺序结构 在JavaScript中&#xff0c;顺序结构是最基本的结构&#xff0c;所谓的顺序结构&#xff0c;指的是代码按照从上到下、…

上海亚商投顾:沪指涨超3% 深成指和创指双双飙涨超6%

上海亚商投顾前言&#xff1a;无惧大盘涨跌&#xff0c;解密龙虎榜资金&#xff0c;跟踪一线游资和机构资金动向&#xff0c;识别短期热点和强势个股。 一.市场情绪 今日A股三大指数一改近期低迷状态&#xff0c;早盘小幅低开后一路高歌猛进集体大涨&#xff0c;沪指涨超3%&am…

锁(二)队列同步器AQS

一、队列同步器AQS 1、定义 用来构建锁或者其他同步组件的基础框架&#xff0c;它使用了一个int成员变量表示同步状态&#xff0c;通过内置的FIFO队列来完成资源获取线程的排队工作。是实现锁的关键。 2、实现 同步器的设计是基于模板方法模式的&#xff0c;也就是说&#…

如何安装x11vnc并结合cpolar实现win远程桌面Deepin

文章目录 1. 安装x11vnc2. 本地远程连接测试3. Deepin安装Cpolar4. 配置公网远程地址5. 公网远程连接Deepin桌面6. 固定连接公网地址7. 固定公网地址连接测试 正文开始前给大家推荐个网站&#xff0c;前些天发现了一个巨牛的 人工智能学习网站&#xff0c; 通俗易懂&#xff…

11.0 Zookeeper watcher 事件机制原理剖析

zookeeper 的 watcher 机制&#xff0c;可以分为四个过程&#xff1a; 客户端注册 watcher。服务端处理 watcher。服务端触发 watcher 事件。客户端回调 watcher。 其中客户端注册 watcher 有三种方式&#xff0c;调用客户端 API 可以分别通过 getData、exists、getChildren …

GLSL ES 1.0

GLSL ES 概述 写在前面 程序是大小写敏感的每一个语句都应该以英文分号结束一个shader必须包含一个main函数&#xff0c;该函数不接受任何参数&#xff0c;并且返回voidvoid main() { }数据值类型 GLSL支持三种数据类型&#xff1a; 整型浮点型&#xff1a;必须包含小数点&…

python 动态显示数据。

界面显示动态的数据。 from time import sleep import serialimport tkinter as tklis[1,10,40] # 打开串行端口 ser serial.Serial(COM3, 9600) # 9600为波特率&#xff0c;根据实际情况进行调整# 创建窗口和画布 window tk.Tk() canvas tk.Canvas(window, width400, heig…

高阶滤波器

一阶后向差分&#xff1a;s&#xff08;1-z^(-1)&#xff09;/T dx/dt[x(k)-x(k-1)]/T[x(k)-x(k)z^(-1)]/Tx(k)*&#xff08;1-z^(-1)&#xff09;/T 一阶前向差分&#xff1a;s(z-1)/T dx/dt[x(k1)-x(k)]/T[z*x(k)-x(k)]/Tx(k)*(z-1)/T 双线性差分&#xff1a;s(2/T)*(1-z…

通过 docker-compose 部署 Flink

概要 通过 docker-compose 以 Session Mode 部署 flink 前置依赖 Docker、docker-composeflink 客户端docker-compose.yml version: "2.2" services:jobmanager:image: flink:1.17.2ports:- "8081:8081"command: jobmanagervolumes:- ${PWD}/checkpoin…

【大模型上下文长度扩展】FlashAttention:高效注意力计算的新纪元

FlashAttention&#xff1a;高效注意力计算的新纪元 核心思想核心操作融合&#xff0c;减少高内存读写成本分块计算&#xff08;Tiling&#xff09;&#xff0c;避免存储一次性整个矩阵块稀疏注意力&#xff0c;处理长序列时的效率问题利用快速 SRAM&#xff0c;处理内存与计算…

【大模型上下文长度扩展】LongQLoRA:单GPU(V100)环境下的语言模型优化方案

LongQLoRA 核心问题子问题1: 预定义的上下文长度限制子问题2: 训练资源的需求高子问题3: 保持模型性能分析不足 LongQLoRA方法拆解子问题1: 上下文长度限制子问题2: 高GPU内存需求子问题3: 精确量化导致的性能损失分析不足效果 论文&#xff1a;https://arxiv.org/pdf/2311.048…

docker镜像结构

# 基础镜像 FROM openjdk:11.0-jre-buster # 设定时区 ENV TZAsia/Shanghai RUN ln -snf /usr/share/zoneinfo/$TZ /etc/localtime && echo $TZ > /etc/timezone # 拷贝jar包 COPY docker-demo.jar /app.jar # 入口 ENTRYPOINT ["java", "-jar"…

游泳耳机推荐性价比排行榜,四大高性价比游泳耳机推荐

随着运动健康意识的提高&#xff0c;越来越多的朋友选择在游泳馆进行锻炼。然而&#xff0c;在水中享受音乐并非易事&#xff0c;这就需要一款真正防水的耳机。尽管市面上有许多声称具备防水功能的耳机产品&#xff0c;但实际使用中往往难以达到理想的防水效果。为了帮助大家找…

之前看过的前序遍历的线索二叉树感觉写的有点问题 这里更新一下我的思路

前序线索化 #include<iostream> using namespace std;typedef int datatype; typedef struct BitNode {datatype Data;struct BitNode* leftchild;struct BitNode* rightchild;int lefttag;int righttag; }Node; #pragma region 前序线索化递归遍历 Node* previous NUL…

maven依赖报错处理(或者maven怎么刷新都下载不了依赖)

maven依赖报错&#xff0c;或者不报错&#xff0c;但是怎么刷新maven都没反应&#xff0c;可以试一下以下操作 当下载jar的时候&#xff0c;如果断网&#xff0c;或者连接超时的时候&#xff0c;会自动在文件夹中创建一个名为*lastupdate的文件&#xff0c;当有了这个文件之后…

网络工程师专属春节对联,不要太真实了!

中午好&#xff0c;我的网工朋友。 都放假了没&#xff1f;龙年将至&#xff0c;都有啥新年计划&#xff1f; 过年&#xff0c;讲究的就是一个热闹&#xff0c;可以暂时告别辛苦的一年&#xff0c;重新整装出发。 热闹可少不了春联啊&#xff0c;红红火火又一年&#xff0c;…

Vue源码系列讲解——虚拟DOM篇【一】(Vue中的虚拟DOM)

目录 1. 前言 2. 虚拟DOM简介 2.1什么是虚拟DOM&#xff1f; 2.2为什么要有虚拟DOM&#xff1f; 3. Vue中的虚拟DOM 3.1 VNode类 3.2 VNode的类型 3.2.1 注释节点 3.2.2 文本节点 3.2.3 克隆节点 3.2.4 元素节点 3.2.5 组件节点 3.2.6 函数式组件节点 3.2.7 小结 3…