JUC-synchorized与锁原理、锁的升级与膨胀

syn-ed

是一个可重入、不公平的重量级锁synchronized使用对象锁保证了临界区代码的原子性,无论使用synchorized锁的是代码块还是方法,其本质都是锁住一个对象。

  • 同步代码块,锁住的是括号里的对象
  • 同步方法
    • 普通方法,锁住的是当前实例对象,即this
    • 静态方法,锁住的是当前类对象,即class对象
// 同步代码块
synchorized(锁对象) {
}// 普通方法
class Test{public synchorized test() {}
}
// 等价于
class Test{public void test() {synchronized(this) {}}
}// 静态方法
class Test{public synchronized static void test() {}
}
// 等价于
class Test{public void test() {synchronized(Test.class) {}}
}

变量线程安全性

  • 成员变量和静态变量
    • 如果没有被共享,那么一定是线程安全的
    • 如果被共享:
      • 只有读操作,一定是线程安全的
      • 有读写操作,存在并发问题
  • 局部变量
    • 局部遍历是线程安全的
    • 但局部变量引用的对象则不一定
      • 如果该对象没有逃离方法的作用访问,它是线程安全的
      • 如果该对象逃离方法的作用范围,存在并发问题

常见的线程安全类

  • String
  • Integer等包装类
  • Integer
  • StringBuffer
  • Random
  • Vector
  • Hashtable
  • java.util.concurrent 包下的类
    String、Integer 等都是不可变类,因为其内部的状态不可以改变,因此它们的方法都是线程安全的,多个线程调用它们同一个实例的某个方法时,是线程安全的。

锁原理

Monitor

监视器或管程,每一个java对象都可以关联一个Monitor对象,使用synchronized给一个对象加锁(重量级锁),该对象的对象中的Mark Word就被设置指向一个Monitor对象的指针,这其实也就是重量级锁的加锁过程。

Mark Word

java对象的对象头由Mark Word、类型指针、数组长度(如果该对象是一个数组)组成。Mark Word的长度由32bit/64bit。Mark Word里默认存储对象的HashCode、分代年龄和锁标记位

  • 32位的Mark Word
    ![[Pasted image 20240719211851.png]]

  • 64位的Mark Word:
    ![[Pasted image 20240719211811.png]]

Monitor的工作流程

一个对象对应一个Monitor对象。

  • 开始时Monitor中Owner为null
  • 当 Thread-2 执行 synchronized(obj) 就会将 Monitor的所有者Owner置为Thread-2,Monitor中只能有一个Owner,obj对象的Mark Word指向Monitor,把对象原有的Mark Word存入线程栈中的锁记录
    ![[Pasted image 20240719212720.png]]
  • 在 Thread-2 上锁的过程,Thread-3、Thread-4、Thread-5 也执行 synchronized(obj),就会进入 EntryList BLOCKED(双向链表)
  • Thread-2 执行完同步代码块的内容,根据 obj 对象头中 Monitor 地址寻找,设置 Owner 为空,把线程栈的锁记录中的对象头的值设置回 MarkWord
  • 唤醒 EntryList 中等待的线程来竞争锁,竞争是非公平的,如果这时有新的线程想要获取锁,可能直接就抢占到了,阻塞队列的线程就会继续阻塞
  • WaitSet 中的 Thread-0,是以前获得过锁,但条件不满足进入 WAITING 状态的线程(wait-notify 机制)
    在这里插入图片描述

锁升级

随着竞争的增加,只能锁升级,不能降级

无锁 -> 偏向锁 -> 轻量级锁 -> 重量级锁	

偏向锁

在大多数情况下,锁总是由一个线程多次获得,让线程获得锁的代价更低而引入了偏向锁。就和名字一样,是偏向的:

  • 当线程第一次获得锁对象时,其进入偏向状态,该对象的后三位是101,同时使用CAS操作将线程ID记录到Mark Word。如果CAS操作成功,这个线程以后进入这个锁相关的同步块,查看这个线程ID是自己的就表示没有竞争,就不需要再进行任何同步操作。
  • 当另一个线程也尝试获取这个锁对象时,也会使用CAS进行替换,此时一定失败,偏向状态就会结束,撤销偏向后恢复到未锁定或轻量级锁状态。

在java中是默认开启偏向锁的,也就是说在一个对象创建的时候,其Mark Word的后三位是101,其余值均为0;当调用这个对象的hashCode时,就再也无法进入偏向状态了,即后三位是001。这是因为Mark Word会被hashCode占用。

偏向锁的撤销

  • 第一点就是我们前边提过的,调用该对象的hashcode方法。
  • 当有其它线程使用偏向锁对象时,会将偏向锁升级为轻量级锁
  • 调用 wait/notify,需要申请 Monitor,进入 WaitSet

轻量级锁

一个对象有多个线程要加锁,但加锁的时间是错开的(没有竞争),轻量级锁的使用对我们程序员来说,也是使用syn-ed来完成,只是底层的实现对我们透明的。

加锁过程
当某一个线程对一个对象进行加锁时,会在栈帧中创建一个锁记录(Lock Record),并使用==CAS将对象的Mark Word中的信息保存到该锁记录中,而Mark Word中就记录该锁记录的地址,以及锁标志位(00)。==这样就完成了加锁。但是当CAS失败的时候,此时会有两种情况导致失败:

  • 它线程已经持有了该Object的轻量级锁,我又想去获取,这时表明有竞争,进入锁膨胀过程在膨胀之前还有一个自旋的过程
  • 线程自己执行synchronized锁重入,栈帧中还会存在一条Lock Record作为重入的计数,但是每次重入都会有CAS,所以才引入了偏向锁进行优化

解锁过程(当退出synchronized代码块)

  • 如果有取值为null的锁记录,表示有重入,这时重置锁记录,表示重入计数减 1
  • 如果锁记录的值不为null,这时使用CAS将 Mark Word的值恢复给对象头
    • 成功,则解锁成功
    • 失败,说明轻量级锁进行了锁膨胀或已经升级为重量级锁,进入重量级锁解锁流程。

重量级锁

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

  • 当线程1使用CAS对一个对象obj进行加锁时,发现线程1已经持有该轻量锁,此时就会失败并导致锁膨胀
  • 然后就会为obj对象申请Monitor锁,通过obj对象头获取到持锁线程,将Monitor的 Owner置为线程0,将obj的对象头指向重量级锁地址,然后自己进入Monitor的EntryList而BLOCKED。
  • 当线程0释放锁时,使用CAS将Mark Word的值恢复给对象头失败,这时进入重量级解锁流程,即按照 Monitor地址找到Monitor对象,设置Owner为 null,唤醒EntryList中BLOCKED线程
    ![[Pasted image 20240719223237.png]]

![[Pasted image 20240719222445.png]]

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

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

相关文章

Adobe“加速”创意人士开启设计新篇章

近日,Adobe公司宣布了其行业领先的专业设计应用程序——Adobe Illustrator和Adobe Photoshop的突破性创新。这一重大更新不仅为创意专业人士带来了前所未有的设计可能性和工作效率提升,还让不论是插画师、设计师还是摄影师,都能从中受益并创作…

GO内存分配详解

文章目录 GO内存分配详解一. 物理内存(Physical Memory)和虚拟内存(Virtual Memory)二. 内存分配器三. TCMalloc线程内存(thread memory)页堆(page heap)四. Go内存分配器mspanmcachemcentralmheap五. 对象分配流程六. Go虚拟内存ArenaGO内存分配详解 这篇文章中我将抽丝剥茧,…

Redisson中RQueue的使用场景附一个异步的例子

RQueue 是一个基于 Redis 的分布式作业队列系统,它允许开发者在 Ruby 应用程序中实现异步任务处理和计划任务调度。由于 Redis 提供了高性能的内存数据结构存储,RQueue 可以快速地存储和检索队列中的任务,这使得它非常适合于高并发和低延迟的…

【Langchain大语言模型开发教程】评估

🔗 LangChain for LLM Application Development - DeepLearning.AI 学习目标 1、Example generation 2、Manual evaluation and debug 3、LLM-assisted evaluation 4、LangChain evaluation platform 1、引包、加载环境变量; import osfrom dotenv imp…

UVM-config_db机制和用法

1.用途 config_db机制用于在UVM验证平台间传递参数,通常成对出现,其中set相当于寄信,get相当于收信。UVM提供的config_db机制可在组件实例化前就设定好配置信息,这样就可在tb的initial块中就进行设定了。真正将这些配置信息落实在…

RK3568 Linux 平台开发系列讲解(内核入门篇):如何高效地阅读 Linux 内核设备驱动

在嵌入式 Linux 开发中,设备驱动是实现操作系统与硬件之间交互的关键。对于 RK3568 这样的平台,理解和阅读 Linux 内核中的设备驱动程序至关重要。 1. 理解内核架构 在阅读设备驱动之前,首先要了解 Linux 内核的基本架构。内核主要由以下几个部分组成: 内核核心:处理系…

【word转pdf】【最新版本jar】Java使用aspose-words实现word文档转pdf

【aspose-words-22.12-jdk17.jar】word文档转pdf 前置工作1、下载依赖2、安装依赖到本地仓库 项目1、配置pom.xml2、配置许可码文件(不配置会有水印)3、工具类4、效果 踩坑1、pdf乱码2、word中带有图片转换 前置工作 1、下载依赖 通过百度网盘分享的文…

Golang实现免费天气预报获取(OpenWeatherMap)

最近接到公司的一个小需求,需要天气数据,所以就做了一个小接口,供前端调用 这些数据包括六个元素,如降水、风、大气压力、云量和温度。有了这些,你可以分析趋势,知道明天的数据来预测天气。 1.1 工具简介 …

《Java8函数式编程》学习笔记汇总

前言 见证了java8的多层排序,为此想系统学习下java8的用法。 目录 简介Lambda表达式流高级集合类和收集器数据并行化测试、调试和重构设计和架构的原则使用Lambda表达式编写并发程序下一步改怎么办 后记

tinyxml2的入门教程

tinyxml2的入门教程 前言一、tinyxml2 创建xml 文件二、tinyxml2 添加数据三、tinyxml2 更改数据四、tinyxml2 删除数据五、tinyxml2 打印总结 前言 xml 是一种标记型文档,有两种基本解析方式:DOM(Document Object Model,文档对象模型)和SAX…

尚品汇-sku存入Redis缓存(二十三)

目录: (1)分布式锁改造获取sku信息 (2)使用Redisson 分布式锁 AOP实现缓存 (3)定义缓存aop注解 (1)分布式锁改造获取sku信息 前面学习了本地锁的弊端,…

NFTScan 浏览器现已支持 .mint 域名搜索功能!

近日,NFT 数据基础设施 NFTScan 浏览器现已支持用户输入 .mint 域名进行 Mint Blockchain 网络钱包地址的搜索查询, NFTScan 用户能够轻松地使用域名追踪 NFT 交易,为 NFT 钱包地址相关的搜索查询功能增加透明度和便利性。 NFTScan explorer…

MATLAB算法实战应用案例精讲-【数模应用】Poisson 回归分析(附R语言、python和MATLAB代码实现)

目录 前言 知识储备 常见回归方法 一、 回归分析方法概述 二、 分类 1.应用领域分类 (1)通用型 (2)统计学角度 (3)计量角度 (4)社科学角度 (5)医学角度 (6)数学建模 (7)专业型 Poisson回归和负二项回归 1.前提条件 2.分析流程图 3.案例分析 算…

通讯规约协议

通讯规约协议(Communication Protocol),又称为通信规程,是随着现代通信技术和计算机网络技术的发展而发展的规约。它是通信双方对数据传送控制的一种约定,包括对数据格式、同步方式、传送速度、传送步骤、检纠错方式以…

Java8-求两个集合取交集

在Java8中,求两个集合的交集可以使用不同的三种方式:传统的循环遍历、使用Stream API的filter操作和使用Stream API的Collection操作。 方法一:传统的循环遍历 首先,我们创建两个集合list1和list2,并给它们添加一些元…

vue + xterm 前端终端terminal

引入 import {Terminal} from "xterm"; import {FitAddon} from "xterm-addon-fit"; import "xterm/css/xterm.css";html <div id"terminal"></div>vue onMounted(() > {nextTick(() > {initTerm();}) })const i…

规划决策算法(四)---Frenet坐标系

知乎&#xff1a;坐标系转换 1.Frenet 坐标系 什么是 Frenet 坐标系&#xff1a; 为什么使用 Frenet 坐标系&#xff1a; 通常情况&#xff0c;我们只会关注车辆当前距离左右车道线的距离&#xff0c;来判断是否偏离车道&#xff0c;是否需要打方向盘进行方向微调。而不是基于…

腾讯云k8s相关

1.某个服务腾讯云内网地址&#xff1f; 比如&#xff1a;spiderflow-web正式环境&#xff1a;http://spiderflow-web.sd-backend:30001 试一试&#xff1a;

python实现批量化查询耗时SQL

python实现批量化查询耗时SQL 今天发现最近多了一些耗时SQL的查询&#xff0c;例如我去数据库一张千万级表查询一天的数据需要耗时20分钟&#xff0c;我总共需要查询一年的数据&#xff0c;我总不能一条一条的手动执行吧&#xff0c;这样也太伤身体&#xff0c;属实难崩啊。就…

笔记——C语言

C语言是一种结构化的程序设计语言 空格的ASCII码值是32&#xff0c;空格是属于可打印字符 \0是字符串结束的标志&#xff0c;字符串的末尾隐藏了一个\0 printf()在打印字符串或者strlen在计算字符串长度的时候&#xff0c;遇到\0的时候就自动停止。 strlen返回的值是size_t…