【并发】并发锁机制-深入理解synchronized(二)

news2024/11/24 7:24:31

【并发】并发锁机制-深入理解synchronized(二)

synchronized 高级篇(底层原理)

synchronized是JVM内置锁,基于Monitor机制实现。

这个Monitor就是管程的意思,它可以控制线程,让其陷入等待,或者将其唤醒

synchronized 依赖底层操作系统的互斥原语Mutex(互斥量),它是一个重量级锁,性能较低。

因为,有使用到操作系统底层的原语Mutex,我们只能通过系统调用来使用它!所以,CPU要从用户态到内核态,它是一个很重的操作!

不过,在JVM内置锁在1.5之后版本做了重大的优化,如锁粗化(Lock Coarsening)、锁消除(Lock Elimination)、轻量级锁(Lightweight Locking)、偏向锁(Biased Locking)、自适应自旋(Adaptive Spinning)等技术来减少锁操作的开销,内置锁的并发性能已经基本与Lock持平。 

根据一些测试报告,在数据量不是很大的情况下,synchronized的性能大约只比ReentrantLock 差10%-20%! 

一、查看synchronized的字节码指令序列

Java虚拟机通过一个同步结构支持方法方法中的指令序列的同步:monitor

同步方法是通过方法中的access_flags(访问标志位)中设置ACC_SYNCHRONIZED标志来实现。

同步代码块是通过 monitorenter 和 monitorexit 来实现。两个指令的执行是JVM通过调用操作系统的互斥原语mutex来实现,被阻塞的线程会被挂起、等待重新调度,会导致“用户态和内核态”两个态之间来回切换,对性能有较大影响。

同步方法

private static int counter = 0;

public synchronized static void increment() {
    counter++;
}
public synchronized static void decrement() {
    counter--;
}

这里的synchronized加在方法上面,所以方法内部的指令没有发生变化!仅仅是加了一个标志位

这边显示的是0x0029,其实是0x0001 + 0x0008 + 0x0020

同步代码块 

private static String lock = "";

public static void increment() {
    synchronized (lock) {
        counter++;
    }
}

public static void decrement() {
    synchronized (lock) {
        counter--;
    }
}

这里方法内部的指令发生的改变!

【问】为什么monitorexit指令有2次? 

第一个monitorexit指令是同步代码块正常释放锁的一个标志

如果同步代码块中出现Exception或者Error,则会调用第二个monitorexit指令来保证释放锁

二、Monitor(管程/监视器)

Monitor在操作系统中就是管程,而在Java中,我们通常称它为监视器

管程是指管理共享变量以及对共享变量操作的过程,让它们支持并发

在Java 1.5之前,Java语言提供的唯一并发语言就是管程,Java 1.5之后提供的SDK并发包也是以管程为基础的!例如:JUC

synchronized关键字wait()、notify()、notifyAll()这三个方法是Java中实现管程技术的组成部分。

MESA模型

在管程的发展史上,先后出现过三种不同的管程模型——Hasen模型、Hoare模型和MESA模型。

现在正在广泛使用的是MESA模型,介绍如下:

入口只允许一个线程通过,其余的现在入口等待队列中等待!这样子设计可以解决互斥的问题! 

进去之后,里面还提供了条件变量每个条件变量对应有一个等待队列!

条件变量其等待队列的作用是解决线程之间的同步问题!条件队列里面存的东西,可以理解为“被wait()” 的线程。

wait()的正确使用姿势

对于MESA管程来说,有一个编程范式:

while(条件不满足) {
  wait();
}

唤醒的时间获取到锁继续执行的时间是不一致的,被唤醒的线程再次执行时可能条件又不满足了,所以循环检验条件。MESA模型的wait()方法还有一个超时参数,为了避免线程进入等待队列永久阻塞。 

我们可以看看Object类里面的对于 wait() 方法的注解描述:

确实需要将其放在循环里面。 

notify() 和 notifyAll() 分别何时使用

满足以下三个条件时,可以使用notify(),其余情况尽量使用notifyAll()

  1. 所有等待线程拥有相同的等待条件;
  2. 所有等待线程被唤醒后,执行相同的操作;
  3. 只需要唤醒一个线程。

Java语言的内置管程synchronized

Java 参考了 MESA 模型,语言内置的管程(synchronized)对 MESA 模型进行了精简。MESA 模型中,条件变量可以有多个,Java 语言内置的管程里只有一个条件变量。模型如下图所示。 

Monitor机制在Java中的实现

java.lang.Object 类定义了 wait(),notify(),notifyAll() 方法,这些方法的具体实现,依赖于 ObjectMonitor 实现,这是 JVM 内部基于 C++ 实现的一套机制。

 ObjectMonitor其主要数据结构如下(hotspot源码ObjectMonitor.hpp):

ObjectMonitor() {
    _header       = NULL; //对象头  markOop
    _count        = 0;  
    _waiters      = 0,   
    _recursions   = 0;   // 锁的重入次数 
    _object       = NULL;  //存储锁对象
    _owner        = NULL;  // 标识拥有该monitor的线程(当前获取锁的线程) 
    _WaitSet      = NULL;  // 等待线程(调用wait)组成的双向循环链表,_WaitSet是第一个节点
    _WaitSetLock  = 0 ;    
    _Responsible  = NULL ;
    _succ         = NULL ;
    _cxq          = NULL ; //多线程竞争锁会先存到这个单向链表中 (FILO栈结构)
    FreeNext      = NULL ;
    _EntryList    = NULL ; //存放在进入或重新进入时被阻塞(blocked)的线程 (也是存竞争锁失败的线程)
    _SpinFreq     = 0 ;
    _SpinClock    = 0 ;
    OwnerIsThread = 0 ;
    _previous_owner_tid = 0;

图解Java中的Monitor机制

在获取锁时,是将当前线程插入到cxq的头部,而释放锁时,默认策略(QMode=0)是:如果EntryList为空,则将cxq中的元素按原有顺序插入到EntryList,并唤醒第一个线程,也就是当EntryList为空时,是后来的线程先获取锁。_EntryList不为空,直接从_EntryList中唤醒线程。

【思考】synchronized加锁加在对象上,锁对象是如何记录锁状态的? 

三、对象的内存布局

Hotspot虚拟机中,对象在内存中存储的布局可以分为三块区域:对象头(Header)、实例数据(Instance Data)和对齐填充(Padding)。

  • 对象头:比如 hash码,对象所属的年代,对象锁,锁状态标志,偏向锁(线程)ID,偏向时间,数组长度(数组对象才有)等。
  • 实例数据:存放类的属性数据信息,包括父类的属性信息;
  • 对齐填充:由于虚拟机要求 对象起始地址必须是8字节的整数倍。填充数据不是必须存在的,仅仅是为了字节对齐。

什么是对象头?

HotSpot虚拟机的对象头包括:

  • Mark Word 

用于存储对象自身的运行时数据,如哈希码(HashCode)、GC分代年龄、锁状态标志、线程持有的锁、偏向线程ID、偏向时间戳等,这部分数据的长度在32位和64位的虚拟机中分别为32bit和64bit,官方称它为“Mark Word”。

  • Klass Pointer

对象头的另外一部分是klass类型指针,即对象指向它的类元数据的指针,虚拟机通过这个指针来确定这个对象是哪个类的实例。 32位4字节,64位开启指针压缩或最大堆内存

  • 数组长度(只有数组对象有)

如果对象是一个数组, 那在对象头中还必须有一块数据用于记录数组长度。 4字节

四、使用JOL工具查看内存布局

五、Mark Word是如何记录锁状态的?

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

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

相关文章

想去看更大的世界,社科院与杜兰大学金融管理硕士项目给予你前行的勇气

当我们的工作生活趋于稳定,我们那颗不安分的心就按捺不住的跳动,想要去看更美的风景,探索更大的世界。所谓遥不可及的梦想才是你见过更大世界的证明,社科院与杜兰大学金融管理硕士项目给予你前行的勇气。一定要不断提高自己的认知…

浅谈函数栈帧(Stack Frame)

💙作者:阿润菜菜 📖专栏:C 本文目录 什么是栈帧 在调试中观察 总结 什么是栈帧 那我们先来看看什么是栈: 栈(stack)是限定仅在表尾进行插入或者删除的线性表。栈是一种数据结构,它按照后进先出的原则存储…

C进阶:字符函数和内存函数

字符串函数和内存函数字符函数和内存函数字符函数求字符串长度strlen长度不受限制的字符串函数strcpystrcatstrcmp长度受限制的字符串函数strncpystrncatstrncmp字符串查找strstrstrtok错误信息报告strerror字符函数:内存函数memcpymemmovememcmpmemset库函数的模拟…

2023年网络安全比赛--跨站脚本攻击中职组(超详细)

一、竞赛时间 180分钟 共计3小时 二、竞赛阶段 竞赛阶段 任务阶段 竞赛任务 竞赛时间 分值 1.访问服务器网站目录1,根据页面信息完成条件,将获取到弹框信息作为flag提交; 2.访问服务器网站目录2,根据页面信息完成条件,将获取到弹框信息作为flag提交; 3.访问服务器网站目录…

javaWeb jsp

概念&#xff1a; Java Server Pages&#xff0c;Java服务端页面。 其中既可以定义 HTML、JS、CSS等静态内容&#xff0c;还可以定义 Java代码的动态内容 JSP HTML Java。最终解析为一个servlet输出给前端。 jsp实践 <dependency> <groupId>javax.servlet…

ASP.NET Core 3.1系列(24)——依赖注入框架之Autofac

1、前言 前面的博客已经介绍过ASP.NET Core中内置IoC容器的使用方法。对于规模较小的项目来说&#xff0c;内置容器完全够用。但在实际开发业务中&#xff0c;一般更推荐开发者使用Autofac作为系统的IoC容器。相较于微软提供的内置容器&#xff0c;Autofac无论是在功能性还是灵…

Python Socket联机自动匹配双人五子棋(含登录注册系统与界面,数据库连接,可作结课作业,可用于学习)

1、前言 首先&#xff0c;关于源码的获取&#xff0c;本人提供了三种方式&#xff1a; 直接从文章里面CtrlC&#xff0c;CtrlV&#xff0c;然后按照我已给的文件结构搞一下即可&#xff1b;通过积分下载上传到CSDN的资源&#xff1b;点开本人的主页&#xff0c;点击“查看详细…

C语言-字符串+内存函数介绍与模拟实现(10)

目录 思维导图&#xff1a; 字符串与内存函数 求字符串长度 strlen 长度不受限制的字符串函数 strcpy strcat strcmp 长度受限制的字符串函数介绍 strncpy strncat strncmp 字符串查找 strstr strtok 错误信息报告 strerror perror 字符操作 内存操作函数 …

Linux之select、poll、epoll讲解

文章目录1 select、poll、epoll1.1 引言1.2 IO和Linux内核发展1.2.1 整体概述1.2.2 阻塞IO1.2.3 非阻塞IO1.2.4 select1.2.5 共享空间1.2.6 零拷贝1.3 select1.3.1 简介1.3.2 select缺点1.4 poll介绍1.4.1 与select差别1.4.2 poll缺点1.5 epoll1.5.1 epoll相关函数1.5.2 epoll优…

详解floor函数、ceil函数和round函数

1.floor函数 功能&#xff1a;把一个小数向下取整 即就是如果数是2.2 &#xff0c;那向下取整的结果就为2.000000 原型&#xff1a;double floor(doube x); 参数解释&#xff1a; x:是需要计算的数 返回值&#xff1a; 成功&#xff1a;返回一个double类型的数&#xff0c;此数…

6-星际密码

题目 星际战争开展了100年之后&#xff0c;NowCoder终于破译了外星人的密码&#xff01;他们的密码是一串整数&#xff0c;通过一张表里的信息映射成最终4位密码。表的规则是&#xff1a;n对应的值是矩阵X的n次方的左上角&#xff0c;如果这个数不足4位则用0填充&#xff0c;如…

C语言-自定义类型-结构体(11.1)

目录 思维导图&#xff1a; 1.结构体类型的基础知识 1.1结构体的声明 1.2特殊的声明 2.结构的自引用 3.结构体变量的定义和初始化 4.结构体内存对齐 4.1如何计算 4.2如何修改内对齐数 5.结构体传参 写在最后&#xff1a; 思维导图&#xff1a; 1.结构体类型的基础知…

Leetcode:98. 验证二叉搜索树(C++)

目录 问题描述&#xff1a; 实现代码与解析&#xff1a; 递归&#xff1a; 原理思路&#xff1a; 迭代&#xff08;中序&#xff09;&#xff1a; 思路原理&#xff1a; 问题描述&#xff1a; 给你一个二叉树的根节点 root &#xff0c;判断其是否是一个有效的二叉搜索树。…

【目标检测】基于yolov6的钢筋检测和计数(附代码和数据集)

写在前面: 首先感谢兄弟们的订阅,让我有创作的动力,在创作过程我会尽最大能力,保证作品的质量,如果有问题,可以私信我,让我们携手共进,共创辉煌。 Hello,大家好,我是augustqi。 今天给大家分享的目标检测项目是:基于yolov6的钢筋检测和计数实战项目(附代码和数据集…

如何成功发送一个Target 846 EDI报文?

Target塔吉特公司是仅次于沃尔玛的第二大零售百货集团&#xff0c;为客户提供当今时尚前沿的零售服务&#xff0c;物美价廉。而EDI&#xff08;电子数据交换&#xff09;是Target与供应商进行业务往来时要求使用的数据交换方式&#xff0c;具有安全可靠、高效和降低人工成本等优…

磨金石教育摄影技能干货分享|有哪些让你难以忘怀的人文摄影照片

在摄影分类中&#xff0c;人文摄影往往没有明确的释义。它既有纪实摄影的真实&#xff0c;又有艺术摄影的深奥。实际上&#xff0c;人文摄影可以说是二者的结合&#xff0c;在创意和表达上更倾向于艺术性&#xff0c;在画面上更有真实感。1 大雨滂沱这张肖像照极具张力&#xf…

智能家居给我们带来了什么?华秋携手信威安防传感器助力提升家居安全性

智能家居的出现&#xff0c;极大地方便了人们的生活&#xff0c;为生活提供便利舒适的体验&#xff1b;如同洗衣机与洗碗机解放了我们双手一样的道理&#xff0c;智能家居是在生活方方面面为我们了提供最大化的便利可能性。 那么&#xff0c;智能家居是如何为我们生活提供便利…

Jmeter@测试场景

目录 性能测试Jmeter&#xff0c;常用的场景 场景一&#xff1a;Thread Group 场景二、jpgc - Stepping Thread Group 场景三、jpgc - Ultimate Thread Group 场景一&#xff1a;Thread Group 参数配置-线程属性Thread Properties&#xff1a; 1.线程数(Number of Threads)…

并查集的使用

目录 一.介绍 二.并查集的实现 三路径压缩 四.相关题型 4.1省份数量 一.介绍 什么是并查集&#xff1f; 将n个不同的元素划分成一些不相交的集合。开始时&#xff0c;每个元素自成一个 单元素集合&#xff0c;然后按一定的规律将归于同一组元素的集合合并。在这个过程中要…

十五、Express 中使用JWT进行登录验证

cookie 篇 : Node.js 中 cookie的验证登录 | session 篇 : Node.js 中 session验证登录 在前面讲过了两种验证登录的方式&#xff0c;其一是cookie&#xff0c;其二是session&#xff1b;那么在讲JWT之前先来简单的回顾这两种方式区别&#xff1b;cookie和sessi…