【并发】Synchronized的底层原理

news2025/1/19 11:23:47

基本概念

Synchronized【对象锁】采用互斥的方式让同一时刻最多只有一个线程能够持有【对象锁】,如果其他线程想要获取这个【对象锁】就会被阻塞住

底层实现原理

我们可能听过,synchronized底层是通过Monitor来实现的,但如何直观的观察呢?可以通过反汇编来看一下,以下是一段简单的加锁代码

我们通过java -v 文件名.class 来反汇编一下

 可能会疑问为什么会释放两遍锁呢?是因为防止第一遍释放锁的时候会报错,导致死锁的问题,为了以防万一所以设置的释放两次锁

Monitor

Monitor被翻译成监视器,是由JVM提供的,由C++来实现,它的结构如下

当线程到来的时候,线程首先会判断Monitor中的Owner是否为空,如果为空则当前线程就会持有锁,如果不为空,那么当前线程会进入导EntryList中进行等待,此时里面的线程出于Blocked的状态,当Owner为空的时候,这些阻塞的线程就会争抢Owner;而其中的WaitSet是存储的调用wait方法的等待的线程,就是处于Waiting状态的线程

以上所说的都是浅层的Synchronized的底层原理,更深层此的还要继续往下面看

Synchronized的锁升级

Monitor实现的锁属于重量级锁,涉及到用户态(java代码)和内核态(CPU层面)的切换、进程上下文的切换,成本比较高,性能比较低

所以在JDK1.6 引入了两种新型的锁机制:偏向锁轻量级锁,他们的要引入是为了解决在没有多线程竞争或者基本没有竞争的场景下重量级锁机制带来的性能开销

要了解锁升级的流程,我们就需要先了解一下对象的内存结构

对象的内存结构

在HotSpot虚拟机中,对象在内存存储的布局可以分为三个区域,对象头、实例数据和对齐填充。其中对象头对于锁升级的关系是很大的

对象头的结构分为32位和64位,这里我们主要是介绍32位的对象头

  • hashcode:25位的对象标识Hash码
  • age:对象分代年龄占4位
  • biased lock:偏向锁标识,占1位,0表示没有开始偏向锁,1表示开启了偏向锁
  • thread:持有偏向锁的线程ID,占23位
  • epoch:偏向时间戳,占2位
  • ptr to lock record:轻量级锁状态下,指向栈中锁记录的指针,占30位
  • ptr to heavyweight monitor:重量级锁状态下,指向对象监视器Mo nitor的指针,占30位

对象是如何关联Monitor

每个Java对象都可以关联一个Monitor对象,让如果使用synchronized给对象上锁(重量级锁)之后,该对象头的Mark Word就被设置只想Monitor对象的指针,就是上文我们所提到的ptr to heavyweight monitor

轻量级锁

在很多的情况下,在Java程序运行的时候,同步块中的代码都是不存在竞争的,不同的线程交替的执行同步代码块。这种情况下,用重量级锁是没有必要的,所以JVM就引入了轻量级锁

如上述代码,obj这个对象获取了方法一和方法二两个方法的锁,如果现在来了一个线程调用了method1,之后又进入了method2,此时当前线程就进入了同样一把锁两次(重入性),由于是同一个线程所持有的锁,所有就没有必要出现锁的竞争,所以可以使用轻量级锁

下面我们介绍一下轻量级锁的执行流程

 加锁流程

  1. 在线程栈中创建一个Lock Record,将其obj字段指向锁对象
  2. 通过CAS指令将Lock Record的地址存储在对象头的mark word中,如果对象处于无锁的状态则修改成功,代表该线程获取到了轻量级锁
  3. 如果是当前线程已经持有来该锁了,代表这是一次锁的重入。设置Locak Record第一部分位null,起到一个重入计数器的作用
  4. 如果CAS失败,说明发生了竞争,此时就需要转换为欸重量级锁

解锁流程

  1. 遍历线程栈,找到所有obj字段等于当前锁对象的Lock Record
  2. 如果Lock Record的Mark Word为空,代表这是一次可重入,将obj设置为空后继续
  3. 如果Lock Record的Mard Word不为空,则利用CAS指令将对象头中的Mark Word恢复成无锁的状态,如果失败,则膨胀为重量级锁

偏向锁 

轻量级锁在没有竞争时(就自己这个线程),每次重入仍然需要执行 CAS 操作。

Java6 中引入了偏向锁来做进一步优化:只有第一次使用 CAS 将线程 ID 设置到对象的 Mark Word 头,之后发现这个线程 ID 是自己的就表示没有竞争,不用重新 CAS。以后只要不发生竞争,这个对象就归该线程所有,由此可以知道偏向锁的性能比轻量级锁的性能还要好

我们可以看一下偏向锁的内存结构:

它是由biased lock偏向锁标识,占1位,0表示没有开始偏向锁,1表示开启了偏向锁来表示的

下面我们介绍一下偏向锁的执行流程

锁是如何升级的呢

Java中synchronized有偏向锁、轻量级锁、重量级锁三种形式,分别对应了锁只被一个线程持有、不同线程交替持有、多个线程竞争锁的三种情况了

所以一旦锁发生了竞争,锁就会升级为重量级锁 

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

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

相关文章

kingbase重置序列

1、建立表接口 drop table if exists ncc_apple; CREATE TABLE ncc_apple ( id BIGSERIAL NOT null PRIMARY KEY, apple_name VARCHAR(128), creator BIGINT, create_time timestamp DEFAULT CURRENT_TIMESTAMP, updater INT8, update_time timestamp …

欧洲历史的五个阶段

欧洲的历史基本上都是分裂的,大致可以分为五个时期,分别为古希腊时代、罗马帝国时代、中世纪时代,文艺复兴时代、工业革命时代。 一,古希腊时代 古希腊是西方文明的源头,也是最重要和最直接的文明起源,首…

2024年社会发展与管理创新科学国际学术会议(ICSDMIS 2024)

2024年社会发展与管理创新科学国际学术会议(ICSDMIS 2024) 2024 International Conference on Social Development and Management Innovation Science(ICSDMIS 2024) 会议简介: 2024年社会发展与管理创新科学国际学术…

ClickHouse内幕(1)数据存储与过滤机制

本文主要讲述ClickHouse中的数据存储结构,包括文件组织结构和索引结构,以及建立在其基础上的数据过滤机制,从Part裁剪到Mark裁剪,最后到基于SIMD的行过滤机制。 数据过滤机制实质上是构建在数据存储格式之上的算法,所…

MAB规范(1):概览介绍

前言 MATLAB的MAAB(MathWorks Automotive Advisory Board)建模规范是一套由MathWorks主导的建模指南,旨在提高基于Simulink和Stateflow进行建模的代码质量、可读性、可维护性和可重用性。这些规范最初是由汽车行业的主要厂商共同制定的&…

Python语法详解module2(运算符、表达式、流程控制)

目录 一、运算符1. 算术运算符(Arithmetic Operators)2. 比较运算符(Comparison Operators)3. 赋值运算符(Assignment Operators)4. 逻辑运算符(Logical Operators)5. 位运算符&…

低代码专题 | 什么是低代码?低代码是什么意思?最详细解释!

什么是低代码,低代码是什么意思?低代码到底有什么用?企业该如何用低代码赋能?......因为现在太多碎片化信息了,所以大家对于一个概念的理解都是零散的。 故给大家开一个专题,将低代码给大家掰开揉碎了讲清…

[C++]vector的模拟实现

下面是简单的实现vector的功能,没有涉及使用内存池等复杂算法来提高效率。 一、vector的概述 (一)、抽象数据类型定义 容器:向量(vector)vector是表示大小可以变化的数组的序列容器。像数组一样&#xf…

JavaWeb基础(JQuery,XML及解析)

这个阶段有点拖沓了,因为事情比较多,耽搁了一段时间,学习的主要内容为JQuery和XML,因为vue的出现,JQuery技术现在已经不流行了,但是不流行不代表我不会,JQuery最最最最核心的就是他的$()核心函数…

关于yolov8识别滑块关键点

1,images,annotations创建 IMAGES:放图片材料的 ANNTATIONS:放labelImg标记的xml文件 2,labels,txt怎么来的 labels :可以手动创建,里面还配置了train,val,test文件夹。可手动(以下代码中没有写) txt:由一下代码自动生成,前提是images,annotations需要自己去创建 …

从零入手人工智能(1)——卷积神经网络

1.前言 本人作为一名单片机工程师,近期对人工智能领域产生了浓厚的兴趣,并开始了我的探索之旅。人工智能是一个博大精深的领域,我相信有许多志同道合的朋友也希望涉足这个领域,因此我写下这篇文章,旨在详细记录我学习…

Python 组合序号

import pandas as pd # 创建一个示例数据框 data { group: [A, A, A, B, B, C, C, C, C], value: [3, 1, 2, 5, 4, 6, 9, 7, 8] } df pd.DataFrame(data) # 先按group分组,再按value列升序排序 df_sorted_asc df.sort_values(by[group, value]) # 使…

【JavaEE 进阶(二)】Spring MVC(下)

❣博主主页: 33的博客❣ ▶️文章专栏分类:JavaEE◀️ 🚚我的代码仓库: 33的代码仓库🚚 🫵🫵🫵关注我带你了解更多进阶知识 目录 1.前言2.响应2.1返回静态界面2.2返回数据2.3返回HTML代码 3.综合练习3.1计算器3.2用户登…

JAVA小案例-分别计算100以内奇数和偶数的和

JAVA小案例-分别计算100以内奇数和偶数的和 没啥可说的,就是for循环加if分支,也可以用while写。 代码如下: public class Jiouhe {/*** 分别计算100以内奇数和偶数的和* param args*/public static void main(String[] args){int sum10;in…

【CT】LeetCode手撕—146. LRU 缓存

目录 题目1-思路1-1 LRU知识点1-2 实现思路LRU的子数据结构① 双向链表 DLinkedNode 结点定义② 其他字段 LRU实现的方法① 初始化——LRUCache中初始化② public int get(int key) 取元素方法③ public void put(int key, int value) 存元素方法 2-实现⭐146. LRU 缓存——题解…

mysql引入表名称的注意事项

1、遇到问题 mapper中的文件是这样的 解析出来的sql是这样的 sql显示为:select * from ‘tableName’ 2、解决方法 mapper文件种使用${tableName}而不是#{tableName}

用户画像知识点补充——多数据源

引入 针对用户画像项目来说(产品)必须要支持从多种数据源加载业务数据,构建用户标签。 在之前的标签模型开发中,主要是为了简化开发复杂度,业务数据统一存储到HBase表中。 数据源包含如下几个方面: 存储H…

【面向就业的Liux基础】从入门到熟练,探索Linux的秘密(一)

主要帮助大家面向工作过程中Linux系统常用的命令联系,采用极致的实用主义,帮助大家节省时间。 文章目录 前言 一、linux系统 二、linux系统基本命令 1.Linux系统的目录结构 2. 常用命令介绍 3.命令演示 4.作业练习 总结 前言 主要帮助大家面向工作过程中…

人脸识别——OpenCV

人脸识别 创建窗口创建按钮设置字体定义标签用于显示图片选择并显示图片检测图片中的人脸退出程序返回主界面 创建窗口 导入tkinter库,创建窗口,设置窗口标题和窗口大小。 import tkinter as tkwin tk.Tk() win.title("人脸识别") win.geom…

自动控制:控制系统的灵敏度分析

自动控制:控制系统的灵敏度分析 引言 灵敏度问题在控制系统设计中至关重要。灵敏度衡量的是系统对参数变化和扰动的响应程度。本文将详细探讨灵敏度函数的概念,并推导出开环和闭环控制系统在前向路径和反馈路径元素扰动下的灵敏度表达式。 灵敏度概念…