【Java并发】synchronized关键字的底层原理

news2024/11/17 11:40:32

文章目录

    • 1.synchronized作用
    • 2.synchronized加锁原理
    • 3.monitor锁
    • 4.synchronized锁的优化
      • 4.1.自适应性自旋锁
      • 4.2.偏向锁
      • 4.3.轻量级锁
      • 4.3.重量级锁
    • 5.总结

1.synchronized作用

synchronized是Java提供一种隐式锁无需开发者手动加锁释放锁。保证多线程并发情况下数据的安全性,实现了同一个时刻只有一个线程能访问资源,其他线程只能阻塞等待,简单说就是互斥同步。

2.synchronized加锁原理

代码块加锁:
例如下面一段代码就是加上了对象锁
在这里插入图片描述
这个时候通过反编译查看class字节码信息:
在这里插入图片描述
可以看到,底层是通过monitorentermonitorexit两个关键字实现的加锁释放锁,执行同步代码之前使用monitorenter加锁,执行完同步代码使用monitorexit释放锁,抛出异常的时候也是用monitorexit释放锁。

在方法上加锁
在这里插入图片描述
反编译看一下底层实现
在这里插入图片描述
这次只使用了一个ACC_SYNCHRONIZED关键字,实现了隐式的加锁与释放锁。其实无论是ACC_SYNCHRONIZED关键字,还是monitorenter和monitorexit,底层都是通过获取monitor锁来实现的加锁释放锁

3.monitor锁

Monitor 被翻译为监视器,是由jvm提供,c++语言实现

monitor锁是通过ObjectMonitor来实现的,虚拟机中ObjectMonitor数据结构如下(C++实现的):
在这里插入图片描述
其中:

  1. Owner(持有锁的线程-只能有一个):存储当前获取锁的线程的,只能有一个线程可以获取
  2. EntryList(保存竞争的线程):关联没有抢到锁的线程,处于Blocked状态的线程
  3. WaitSet:关联调用了wait方法的线程,处于Waiting状态的线程

执行逻辑:
在这里插入图片描述
在这里插入图片描述
图上展示了ObjectMonitor的基本工作机制:

  1. 当多个线程同时访问一段同步代码时,首先会进入 _EntryList 队列中等待。
  2. 当某个线程获取到对象的Monitor锁后进入临界区域,并把Monitor中的 _owner变量设置为当前线程,同时Monitor中的计数器 _count 加1。即获得对象锁。
  3. 若持有Monitor的线程调用 wait()方法,将释放当前持有的Monitor锁,_owner变量恢复为null_count减1,同时该线程进入 _WaitSet集合等待被唤醒。
  4. 在_WaitSet 集合中的线程唤醒后会被再次放到_EntryList 队列中,重新竞争获取锁。
  5. 若当前线程执行完毕也将释放Monitor并复位_owner变量的值,以便其他线程进入获取锁。

4.synchronized锁的优化

JDK1.5之前,synchronized是属于重量级锁(Monitor实现的锁属于重量级锁),涉及到了用户态和内核态的切换进程的上下文切换成本较高性能比较低。
在JDK 1.6引入了两种新型锁机制:偏向锁轻量级锁,它们的引入是为了解决在没有多线程竞争基本没有竞争的场景下因使用传统锁机制带来的性能开销问题。

4.1.自适应性自旋锁

自旋锁:在没有拿到锁的时候,当前线程会进入阻塞状态,当持有锁的线程释放了锁,当前线程才可以再去竞争锁。在线程占用锁的时间很短的话。会浪费大量的性能阻塞和唤醒的切换上。

为了避免阻塞和唤醒的切换,在没有获得锁的时候就不进入阻塞,而是不断地循环检测锁是否被释放,这就是自旋。在占用锁的时间短的情况下,自旋锁表现的性能是很高的。

自适应性自旋锁:是对自旋锁的一次升级,自适应性自旋锁的意思是,自旋的次数不是固定的,而是由前一次在同一个锁上的自旋时间锁的拥有者的状态来决定

举例就是此次自旋成功了,很有可能下一次也能成功,于是允许自旋的次数就会更多,反过来说,如果很少有线程能够自旋成功,很有可能下一次也是失败,则自旋次数就更少。这样能最大化利用资源

4.2.偏向锁

不存在多线程竞争,而且总是由同一线程多次获得锁(同一线程可重入锁)
例如如下代码:
同一个线程多次获得锁
加锁m1—>m2—>m3
释放锁m3—>m2—>m1
期间并不存在竞争,不存在阻塞等待,也不存在唤醒。
在这里插入图片描述
在这里插入图片描述
原理:
锁的争夺实际上是Monitor对象的争夺,还有每个对象都有一个对象头,对象头是由Mark Word和Klass pointer 组成的。一旦有线程持有了这个锁对象,标志位修改为1,就进入偏向模式,同时会把这个线程的ID记录在对象的Mark Word中,当同一个线程再次进入时,就不再进行同步操作,这样就省去了大量的锁申请的操作,从而提高了性能。

只有第一次使用 CAS 将线程 ID 设置到对象的 Mark Word 头,之后发现
这个线程 ID自己的表示没有竞争,不用重新 CAS。以后只要不发生竞争,这个对象就归该线程所有

一旦不同的线程来获取锁的时候,那么偏向锁发现Mark Word中线程id不一样了,就会向上升级为轻量级锁(不会直接升级到重量级锁)

4.3.轻量级锁

在很多的情况下,在Java程序运行时,同步块中的代码都是不存在竞争的不同的线程交替的执行同步块中的代码。(交替执行自然不存在线程竞争)
在这里插入图片描述
原理:
执行同步代码块之前,JVM会在线程的栈帧中创建一个锁记录(Lock Record),并将Mark Word拷贝复制到锁记录中。然后尝试通过CAS操作将Mark Word中的锁记录的指针,指向创建的Lock Record。如果成功表示获取锁状态成功,如果失败,则进入自旋获取锁状态。

在这里插入图片描述

自旋获取锁也失败了,则升级为重量级锁,也就是把线程阻塞起来,等待唤醒。

4.3.重量级锁

也就是上述的synchronized锁,就是一个重量级锁

重量级锁就是一个悲观锁了,但是其实不是最坏的锁,因为升级到重量级锁,是因为线程占用锁的时间长(自旋获取失败),锁竞争激烈的场景,在这种情况下,让线程进入阻塞状态,进入阻塞队列,能减少cpu消耗。所以说在不同的场景使用最佳的解决方案才是最好的技术。synchronized在不同的场景会自动选择不同的锁,这样一个升级锁的策略就体现出了这点。

5.总结

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

对应情况
偏向锁只被一个线程持有
轻量级锁不同线程交替持有锁
重量级锁多线程竞争锁
描述
重量级锁底层使用的Monitor实现,里面涉及到了用户态和内核态的切换、进程的上下文切换,成本较高,性能比较低
轻量级锁线程加锁的时间是错开的(也就是没有竞争),可以使用轻量级锁来优化。轻量级修改了对象头的锁标志,相对重量级锁性能提升很多。每次修改都是CAS操作,保证原子性
偏向锁一段很长的时间内都只被一个线程使用锁,可以使用了偏向锁,在第一次获得锁时,会有一个CAS操作之后该线程再获取锁,只需要判断mark word中是否是自己的线程id即可,而不是开销相对较大的CAS命令

一旦锁发生了竞争,都会升级为重量级锁

参考来自:黑马程序员,公众号:java技术爱好者、一灯架构

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

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

相关文章

dubbo之高可用

负载均衡 概述 负载均衡是指在集群中,将多个数据请求分散到不同的单元上执行,主要是为了提高系统的容错能力和对数据的处理能力。 Dubbo 负载均衡机制是决定一次服务调用使用哪个提供者的服务。 策略 在Dubbo中提供了7中负载均衡策略,默…

1.0 Python 标准输入与输出

python 是一种高级、面向对象、通用的编程语言,由Guido van Rossum发明,于1991年首次发布。python 的设计哲学强调代码的可读性和简洁性,同时也非常适合于大型项目的开发。python 语言被广泛用于Web开发、科学计算、人工智能、自动化测试、游…

C#实现三菱FX-3U SerialOverTcp

设备信息 测试结果 D值测试 Y值写入后读取测试 协议解析 三菱FX 3U系列PLC的通信协议 1. 每次给PLC发送指令后,必须等待PLC的应答完成才能发送下一条指令; 2. 报文都是十六进制ASCII码的形式 3. 相关指令 指令 命令码(ASCII码) 操作原件 …

七牛云获取qn(url、bucket、access-key、secret-key)

1.注册账号 2.access-key和secret-key: 点击“密钥管理” 复制AK和SK即可 域名: bucket: 这个就是对象存储空间名字 先新建一个空间(没买需要先购买),步骤如下: 填写存储空间名字&#xff0…

“OSError: [WinError 1455]页面文件太小,无法完成操作。”解决方案

按照下面步骤, 会发现电脑默认情况下是没有给D盘分配虚拟内存的, 所以将Python装在D盘的朋友, 在跑程序时, 没有分配虚拟内存, 自然就遇到了上面的问题, 所以根本操作只要给D盘分配虚拟内存即可 第一步:鼠标右击我的电脑 (此电脑),点击属性进入以下界面 …

PostMessage/SendMessage在不同线程的调用探究

PostMessage和SendMessage是我们比较常用的windows API,最近也探究这两个api在调用之后,执行的线程问题,发现如下结论: 仅仅是RegisterClass注册类之后,调用createwindow第一个参数通过:(const TCHAR*)base…

three.js 地球与卫星绕地飞行【真实三维数据】

&#xff08;真实经纬度运行轨迹&#xff09; 完整代码 <template><div class"home3dMap" id"home3dMap" v-loading"loading"></div> </template><script> import * as THREE from three import { OrbitControl…

【K8S 的二进制搭建】

目录 一、二进制搭建 Kubernetes v1.201、准备环境 二、操作系统初始化配置三、部署 etcd 集群1、准备签发证书环境2、在 master01 节点上操作1、生成Etcd证书 3、在 node01 节点上操作4、在 node02 节点上操作 四、部署 Master 组件五、部署 docker引擎六、部署 Worker Node 组…

【算法|数组】手撕经典二分法

算法|数组——二分查找 文章目录 算法|数组——二分查找引言二分查找左闭右闭写法左闭右开写法 总结 引言 首先学习这个算法之前需要了解数组知识&#xff1a;数组。 大概介绍以下&#xff1a; 数组是存储在连续内存空间上的相同类型数据的集合。数组下标都是从0开始。数组在…

【SpringCloud】Gateway服务网关

Spring Cloud Gateway 是 Spring Cloud 的一个全新项目&#xff0c;该项目是基于 Spring 5.0&#xff0c;Spring Boot 2.0 和 Project Reactor 等响应式编程和事件流技术开发的网关&#xff0c;它旨在为微服务架构提供一种简单有效的统一的 API 路由管理方式。 1.为什么需要网关…

“实现数字化转型:探索会议OA项目的高级技术与创新应用“

文章目录 引言&#xff1a;1.项目背景和需求分析&#xff1a;2.技术选型和架构设计&#xff1a;3.项目实现和功能亮点&#xff1a;3.0 layui实现登录及注册3.1 会议管理模块3.1.1 会议发布3.1.2 我的会议3.1.3 我的审批3.1.4 会议通知3.1.5 待开会议3.1.6 历史会议3.1.7 所有会…

【Leetcode】层次遍历||树深度||队列

step by step. 题目&#xff1a; 给定一个二叉树 root &#xff0c;返回其最大深度。 二叉树的 最大深度 是指从根节点到最远叶子节点的最长路径上的节点数。 示例 1&#xff1a; 输入&#xff1a;root [3,9,20,null,null,15,7] 输出&#xff1a;3示例 2&#xff1a; 输入&am…

【LeetCode】数据结构题解(13)[设计循环链表]

设计循环链表 &#x1f609; 1.题目来源&#x1f440;2.题目描述&#x1f914;3.解题思路&#x1f973;4.代码展示 所属专栏&#xff1a;玩转数据结构题型❤️ &#x1f680; >博主首页&#xff1a;初阳785❤️ &#x1f680; >代码托管&#xff1a;chuyang785❤️ &…

WordPress使用【前端投稿】功能时为用户怎么添加插入文章标签

在使用Wordpress做前端投稿功能的时候&#xff0c;可能需要用户填写文章标签&#xff0c;在插入文章的时候很多人不知道怎么把这些标签插入进去&#xff0c;下面这篇文章来为大家带来WordPress使用前端投稿功能时插入文章标签方法。 在Wordpress里 wp_insert_post 此函数的作…

Vue3核心笔记

文章目录 1.Vue3简介2.Vue3带来了什么1.性能的提升2.源码的升级3.拥抱TypeScript4.新的特性 一、创建Vue3.0工程1.使用 vue-cli 创建2.使用 vite 创建 二、常用 Composition API1.拉开序幕的setup2.ref函数3.reactive函数4.Vue3.0中的响应式原理vue2.x的响应式Vue3.0的响应式 5…

冠达管理:夜盘是什么意思?

跟着现代社会的快节奏开展&#xff0c;股票商场的运营也因而发生了相应的改变。夜盘是指在正常的买卖时刻外特别敞开的买卖商场&#xff0c;为出资者供给更多的买卖时刻和机会。夜盘的概念在我国自2002年引入以来逐渐被越来越多的出资者所熟知。那么夜盘究竟是什么&#xff1f;…

Collection 中的简单命令

一、list 集合常用的方法 LIst&#xff1a;元素存取有序、可以保存重复元素、有下标。可以存储 null 值。 ArrayList&#xff1a;底层数组结构&#xff0c;有 index&#xff0c;查询快 LinkList&#xff1a; 底层链表结构&#xff0c;增删快 添加 //确保此集合包含指定的元素…

操作系统—进程管理

这里写目录标题 进程特点并行和并发进程的状态进程的控制结构PCB包含什么信息 进程上下文切换进程上下文切换发生的场景 进程的通信方式 线程什么是线程线程的优缺点进程线程的区别线程的上下文切换线程的实现用户线程和内核线程的对应关系 用户线程如何理解、优缺点内核线程如…

在Echarts中的tooltip上添加点击按钮

需求&#xff1a; 在Echarts的tooltips中添加点击按钮并可以鼠标悬停点击该按钮 功能实现&#xff1a; 在option中的tooltip添加enterable: true的属性&#xff0c;表示鼠标可以移入tooltip中再在formatter中添加 <button onclick"onTooltipsFun()" stylecursor:…

protobuf 2定义string常量

背景 protobuf 2中定义的enum枚举值必须为数字类型&#xff0c;故不支持string类型&#xff0c;但有些业务场景又确实需要定义string常量。 目标 在protobuf 2中定义string常量。 方案 思路&#xff1a;通optional default实现string常量。 细节&#xff1a; 1、protobu…