【JavaEE】synchronized原理详解

news2024/9/22 15:33:42

本文使用的是JDK1.8

目录

引言

Java对象在JVM的结构

对象头

 Mark Word

Monitor

Owner

EntryList

WaitSet

加锁过程

锁消除

偏向锁

偏向锁使用

重偏向

撤销偏向

轻量级锁

重量级锁

自旋优化


引言

对于synchronized原理讲解之前,我们需要知道Java对象在JVM中的结构和Monitor是什么。

参考文章:Java对象头详解 - 简书 (jianshu.com)

http://t.csdnimg.cn/uX4RP

黑马程序员深入学习Java并发编程,JUC并发编程全套教程


Java对象在JVM的结构

普通对象

数组对象

其中对象头和加锁关系非常大。所以这里只介绍对象头。

对象头

以32位的JVM为例,如果是64位的,那就×2.

普通对象

|--------------------------------------------------------------|
|                     Object Header (64 bits)                  |
|------------------------------------|-------------------------|
|        Mark Word (32 bits)         |    Klass Word (32 bits) |
|------------------------------------|-------------------------|

数组对象

|---------------------------------------------------------------------------------|
|                                 Object Header (96 bits)                         |
|--------------------------------|-----------------------|------------------------|
|        Mark Word(32bits)       |    Klass Word(32bits) |  array length(32bits)  |
|--------------------------------|-----------------------|------------------------|
 Mark Word
|-------------------------------------------------------|--------------------|
|                  Mark Word (32 bits)                  |       State        |
|-------------------------------------------------------|--------------------|
| identity_hashcode:25 | age:4 | biased_lock:1 | lock:2 |       Normal       |
|-------------------------------------------------------|--------------------|
|  thread:23 | epoch:2 | age:4 | biased_lock:1 | lock:2 |       Biased       |
|-------------------------------------------------------|--------------------|
|               ptr_to_lock_record:30          | lock:2 | Lightweight Locked |
|-------------------------------------------------------|--------------------|
|               ptr_to_heavyweight_monitor:30  | lock:2 | Heavyweight Locked |
|-------------------------------------------------------|--------------------|
|                                              | lock:2 |    Marked for GC   |
|-------------------------------------------------------|--------------------|

64位的如下: 

偏向锁位 + 锁标志位

identity_hashcode:hashcode。只有调用该方法时才会生成。如果调用某个对象未被重写的HashCode方法,此时对它进行上锁,它将直接进入轻量级锁;如果它在偏向锁的基础上,在调用HashCode方法,此时它就变成重量级锁了。

epoch:偏向时间戳。 


Monitor

上面提到了重量级锁,就是和这里的Monitor对象有关。每个Java对象都可以关联一个Monitor对象,使用synchronized加锁的对象如果升级成重量级锁,就要和Monitor对象关联了。

Monitor对象主要由三部分构成

Owner

开始时,Owner为空。如果对象A被Thread-1线程加成了重量级锁,并且它右调用了Object中的wait方法,则它就进入WaitSet中等待,此时其他线程可以对对象A上锁了。Thread-1同理。现在就是Thread-2对对象A上锁了,并且没释放,也没有wait。

EntryList

Thread-3和Thread-4线程也想对对象A上锁,但是此时Thread-2线程持有锁,它俩只能进入EntryList进行等待。如果后续Thread-2线程释放了锁,就会通知EntryList中所有的线程来竞争锁。

WaitSet

原本持有锁,但是使用wait方法后放弃了锁,就进入WatiSet中进行等待。后续只能用对象A的notify()或者notifyAll()方法来唤醒它们。


加锁过程

synchronized的加锁过程是逐步提高的,并不是一上来就要加重量级锁。

锁消除

对于一些对象,如果它不可能被其他线程贡献,并且对于该对象使用synchronized加锁了,那么JIT(即时编译器)会自动的不给这个对象加锁。因为不可能发生锁冲突的情况。如下代码:

public class MyTest {
	static int x = 0;
	public void a() throws Exception {
		x++;
	}
	// 这里的 o 对象是不可能被其他线程使用到的
	public void b() throws Exception {
		Object o = new Object();
		synchronized (o) {
			x++;
		}
	}
}

锁消除是默认开启的。-XX:-EliminateLocks 使用它关闭


 

偏向锁

  • 偏向锁是默认开启的。所以当对象创建后,它的锁标志位后三位为 101,且偏向时间戳为0,也没有线程指针。
  • 偏向锁默认是延迟的。也就是不会在程序启动时立刻生效,如果不想有延迟,可以添加VM参数  -XX:BiasedLockingStartupDelay=0 来禁用。

偏向锁使用

在没有锁竞争的时候,每次重入都需要进行CAS操作(把线程ID记录到对象中),但这个操作在第一次执行完之后如果重入的时候在使用CAS操作没必要。所以在JDK 6 之后就使用偏向锁来优化。

线程第一次使用对象后,就把线程ID记录的对象头中的Mark Word中。后续如果要加锁,先看看线程ID是不是自己,表示重入,就没发生竞争。

重偏向

如果有多个线程访问同一个对象,但是没有发生锁竞争。比如线程1先对对象A加了偏向锁(线程A已经结束),后续线程2又使用了对象A,当访问次数超过20 次后,后续如果线程2还要使用对象A,那么此时对象A的Mark Word中的线程ID就变成了线程2的。

撤销偏向

  • 当上述的重偏向次数超过 40 次后,那么这个类所创建的对象都会变成不可偏向的,新建的对象也都是不可偏向的。
  • 当调用对象的hashCode方法时,偏向锁也会被撤销。如果是轻量级锁,那么hashCode会保存在锁记录中;如果是重量级锁,hashCode会保存在Monitor中。
  • 线程1使用对象时,线程2也来使用相同的对象了,此时也会撤销偏向锁,升级成轻量级锁。
  • 当调用了wait方法后,在notify后,此时就从偏向锁升级成重量级锁了。

轻量级锁

之前谈到,当多个线程对同一对象操作时,锁状态会从偏向锁升级到轻量级锁。轻量级锁是对使用者透明的。

轻量级锁加锁过程如下:

  • 每个线程的栈帧都要创建一个锁记录(Lock Record)的结构,其内部存储锁定对象的Mark Word

 

  • Object reference指向锁对象;并用CAS尝试把对象头中的Mark Word中的跟偏向锁相关的内容存到lock record中,把原来的Mark Word中偏向锁的记录改成存 lock record的地址。如果操作成功,就如下图所示:

  • 如果CAS尝试失败:
    • 可能是其他线程拥有了该对象的轻量级锁。此时就会自选优化,最后升级到重量级锁
    • 也有可能是自己这个线程执行的所重入,那么就会继续增加一条锁记录,不过新加的锁记录的指向地址就是空,后续取到为空的锁记录时,重入记录减一。如下图
  • 轻量级锁解锁时,如果最后一条锁记录为 偏向锁的相关信息,则使用CAS把Mark Word中的恢复
    • 恢复成功,就是解锁成功
    • 恢复失败,说明轻量级锁升级为重量级锁了,要用重量级锁的方式来解锁。

重量级锁

如果在尝试加轻量级锁的过程中,CAS 操作无法成功,这时一种情况就是有其它线程为此对象加上了轻量级锁(有竞争),这时需要进行锁膨胀,将轻量级锁变为重量级锁。

下图中的Thread-0线程已经加了轻量级锁,当Thread-1线程想来加锁时,那就不成功,Object对象就进入锁升级,升级到重量级锁,也就要和刚开始提到的Monitor关联。

Thread-1线程要为 Object 对象申请 Monitor 锁,让 Object 指向重量级锁地址然后自己进入 Monitor 的 EntryList 进行 BLOCKED。

自旋优化

上面Thread-1线程进入EntryList中不是立刻的。它还对Thread-0线程抱有一丝希望,觉得它能马上执行完成,然后释放锁。所以Thread-1在此期间就重试加锁,过程如下:

重试成功:

重试失败:

自旋会占用 CPU 时间,单核 CPU 自旋就是浪费,多核 CPU 自旋才能发挥优势。
在 Java 6 之后自旋锁是自适应的,比如对象刚刚的一次自旋操作成功过,那么认为这次自旋成功的可能性会高,就多自旋几次;反之,就少自旋甚至不自旋,总之,比较智能。
Java 7 之后不能控制是否开启自旋功能。

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

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

相关文章

如何通过DBC文件看懂CAN通信矩阵

实现汽车CAN通信开发,必不可少要用到DBC文件和CAN通信矩阵。 CAN通信矩阵是指用于描述 CAN 网络中各个节点之间通信关系的表格或矩阵。它通常记录了每个节点能够发送和接收的消息标识符(ID)以及与其他节点之间的通信权限。 通信矩阵在 CAN 网…

Redis中数据分片与分片策略

概述 数据分片是一种将数据分割并存储在多个节点上的技术,可以有效提高系统的扩展性和性能。在Redis中,数据分片主要用于解决单个实例存储容量和性能瓶颈的问题。通过将数据分散存储到多个Redis节点中,可以将负载均衡到不同的服务器上&#…

防火墙之双机热备篇

为什么要在防火墙上配置双机热备技术呢? 相信大家都知道,为了提高可靠性,避免单点故障 肯定有聪明的小伙伴会想到那为什么不直接多配置两台防火墙,然后再将他们进行线路冗余,不就完成备份了吗? 答案是不…

UDP客户端、服务端及简易聊天室实现 —— Java

UDP 协议(用户数据包协议) UDP 是无连接通信协议,即在数据传输时,数据的发送端和接收端不建立逻辑连接,简单来说,当客户端向接收端发送数据时,客户端不会确认接收端是否存在,就会发出…

【深度学习教程】

文章目录 pytorch官方教程知识蒸馏:https://pytorch.org/tutorials/beginner/knowledge_distillation_tutorial.html 李宏毅-机器学习/深度学习https://speech.ee.ntu.edu.tw/~hylee/ml/2021-spring.phphttps://speech.ee.ntu.edu.tw/~hylee/ml/2022-spring.phphttp…

深入解析公有IP与私有IP:地址分配与使用限制

IP地址在网络基础设施的建设和维护过程中起着至关重要的作用。作为IP地址的两大类型,公有IP和私有IP各自具有独特的分配机制和使用限制。本文将详细分析两者之间的区别,以帮助读者更好地理解和使用IP地址。 1. 公有IP与私有IP概述 IP地址是网络中的唯一…

Spring Boot 中使用 Resilience4j 实现弹性微服务的简单了解

1. 引言 在微服务架构中,服务的弹性是非常重要的。Resilience4j 是一个轻量级的容错库,专为函数式编程设计,提供了断路器、重试、舱壁、限流器和限时器等功能。 这里不做过多演示,只是查看一下官方案例并换成maven构建相关展示&…

hexo搭建博客(github node git )(失败版本)

HexoGitHub搭建个人博客教程(2023最新版) 搭建失败了 是因为git命令一直报错 打算明天把git和node版本全部重新安装后再弄 同时回顾一下github git 和 node的基础知识 Github新手之路(全过程)(站在前辈的肩膀上的总…

使用 ABBYY FineReader PDF 15 在创建或转换 PDF 时自动生成书签

使用 ABBYY 为 PDF 文件添加书签,可以帮助快速定位文档中的主要内容,也能更方便的梳理出一份文档大纲。 有很多 PDF 文件在创建时并没有编辑书签,这里介绍使用 ABBYY FineReader PDF 15(Win 系统)在 PDF 中自动添加书…

两大国产多模态大模型推荐

GLM-4V 智谱AI 开放平台提供一系列具有不同功能和定价的大模型,包括通用大模型、超拟人大模型、图像大模型、向量大模型等,并且支持使用您的私有数据对模型进行微调。GLM-4V 就是在不牺牲任何NLP任务性能的情况下,实现了视觉语言特征的深度融…

网络编程-TCP/IP

网络概述 网络采用分而治之的方法设计,将网络的功能划分为不同的模块,以分层的形式有机组合在一起。 每层实现不同的功能,其内部实现方法对外部其他层次来说是透明的。每层向上层提供服务,同时使用下层提供的服务 网络体系结构…

【LeetCode】删除排序链表中的重复元素 II

目录 一、题目二、解法完整代码 一、题目 给定一个已排序的链表的头 head , 删除原始链表中所有重复数字的节点,只留下不同的数字 。返回 已排序的链表 。 示例 1: 输入:head [1,2,3,3,4,4,5] 输出:[1,2,5] 示例 …

WEB前端06-DOM对象

BOM浏览器对象模型 浏览器对象模型:将浏览器的各个组成部分封装成对象。是用于描述浏览器中对象与对象之间层次关系的模型,提供了独立于页面内容、并能够与浏览器窗口进行交互的对象结构。 组成部分 Window:浏览器窗口对象 Navigator&…

win11局域网共享打印机或者文件提示:\\计算机名 无法访问。你可没有权限使用网络资源。请与这台服务器的管理员联系以查明你是香有访问权限。网络不存在或尚未启动。解决方法

win11局域网共享打印机或者文件提示:\\计算机名 无法访问。你可没有权限使用网络资源。请与这台服务器的管理员联系以查明你是香有访问权限。网络不存在或尚未启动。 是因为win11更新会默认关闭不安全的SMB1协议,导致局域网共享不可用。既然这样&#xf…

Web开发:四角线框效果(HTML、CSS、JavaScript)

目录 一、实现效果 二、完整代码 三、页面准备 1、页面结构 2、初始样式 3、现有效果 三、线框实现 1、需求分析 2、线框结构 3、线框大小 4、线框位置 5、线框样式 6、移动线框 7、添加过渡效果 8、使用CSS变量 一、实现效果 如下图所示,当鼠标移动…

项目管理进阶之RACI矩阵

前言 项目管理进阶系列续新篇。 RACI?这个是什么矩阵,有什么用途? 在项目管理过程中,如Team规模超5以上时,则有必要采用科学的管理方式,满足工作需要。否则可能事倍功半。 Q:什么是RACI矩阵 …

SQL面试题练习 —— 查询每个用户最大连续登录天数

目录 1 题目2 建表语句3 题解 1 题目 查询每个用户最大连续登录天数 样例数据如下 login_log: 2 建表语句 --建表语句 create table if not exists login_log (user_id int comment 用户id,login_time date comment 登录时间 ); --数据插入 INSERT overwrit…

N Puzzle (数字推盘游戏)

N Puzzle [数字推盘游戏] 1. 15 Puzzle2. N PuzzleReferences puzzle /ˈpʌzl/:n. 谜,智力游戏,疑问,不解之谜,令人费解的事 vt. 迷惑,使困惑1. 15 Puzzle https://en.wikipedia.org/wiki/15_puzzle The…

Python 在Word表格中插入、删除行或列

Word文档中的表格可以用于组织和展示数据。在实际应用过程中,有时为了调整表格的结构或适应不同的数据展示需求,我们可能会需要插入、删除行或列。以下提供了几种使用Python在Word表格中插入或删除行、列的方法供参考: 文章目录 Python 在Wo…

Qt QProcess 进程间通信读写数据通信

本文介绍了如何使用Qt的QProcess 进行程序开发,包括启动进程间通信、设置环境变量、通用方法;方便在日常开发中使用; 1.使用Qt进行程序开发,可以通过QProcess类用于启动外部程序并与其进行通信.; 进程A(…