共享模型之管程(悲观锁)

共享模型之管程(悲观锁)


文章目录

  • 共享模型之管程(悲观锁)
  • 一、常见线程安全的类
  • 二、对象头
  • 三、Monitor(监视器 / 管程)
  • 四、偏向锁
    • 偏向锁的实现原理
    • 撤销偏向锁
  • 五、轻量级锁
    • 轻量级锁的释放
  • 六、重量级锁
  • 七、锁的升级流程
  • 八、sleep / wait / park
    • sleep
    • wait
    • park
  • 九、多把锁相关
  • 十、ReentrantLock


一、常见线程安全的类

  • String
  • Integer
  • StringBuffer
  • Random
  • Vector
  • Hashtable
  • java.util.concurrent 包下的类

他们的每个方法是原子的,但多个方法的组合不是原子的


二、对象头

  • 普通对象头
    在这里插入图片描述
    Mark Word 用来存储对象的 hashCode 或者锁信息等。
    Klass Word 存储到对象类型数据的指针

  • 数组对象头
    在这里插入图片描述
    Array length 存储了数组的长度

  • 其中32位 Mark Word 的结构为
    在这里插入图片描述

  • 其中64位 Mark Word 的结构为
    在这里插入图片描述
    从上到下对应的是无锁、偏向锁、轻量级锁、重量级锁以及GC标志。

可以看到,当对象状态为偏向锁时,Mark Word 存储的是偏向的线程 ID;
当状态为轻量级锁时,Mark Word 存储的是指向线程栈中 Lock Record 的指针;
当状态为重量级锁时,Mark Word 为指向堆中的 monitor(监视器)对象的指针。


三、Monitor(监视器 / 管程)

在 Java 中,监视器(monitor)是一种同步工具,用于保护共享数据,避免多线程并发访问导致数据不一致。在 Java 中,每个对象都有一个内置的监视器。

监视器包括两个重要部分,一个是锁,一个是等待/通知机制,后者是通过 Object 类中的wait(), notify(), notifyAll()等方法实现的。
在这里插入图片描述
刚开始 Monitor 中 Owner 为 null,当 Thread-2 执行 synchronized(obj) 就会将 Monitor 的所有者 Owner 置为 Thread-2,Monitor中只能有一个 Owner。

在 Thread-2 上锁的过程中,如果 Thread-3,Thread-4,Thread-5 也来执行 synchronized(obj),就会进入EntryList BLOCKED,Thread-2 执行完同步代码块的内容,然后唤醒 EntryList 中等待的线程来竞争锁,竞争的时是非公平的。

Owner 线程发现条件不满足,调用 wait 方法,即可进入 WaitSet 变为 WAITING 状态,BLOCKED 和 WAITING 的线程都处于阻塞状态,不占用 CPU 时间片,BLOCKED 线程会在 Owner 线程释放锁时唤醒,WAITING 线程会在 Owner 线程调用 notify 或 notifyAll 时唤醒,但唤醒后并不意味者立刻获得锁,仍需进入EntryList 重新竞争。


四、偏向锁

Hotspot 的作者经过以往的研究发现大多数情况下锁不仅不存在多线程竞争,而且总是由同一线程多次获得,于是引入了偏向锁。

偏向锁会偏向于第一个访问锁的线程,如果在接下来的运行过程中,该锁没有被其他的线程访问,则持有偏向锁的线程将永远不需要触发同步。也就是说,偏向锁在资源无竞争情况下消除了同步语句,连 CAS(后面会细讲,戳链接直达) 操作都不做了,着极大地提高了程序的运行性能。

大白话就是对锁设置个变量,如果发现为 true,代表资源无竞争,则无需再走各种加锁/解锁流程。如果为 false,代表存在其他线程竞争资源,那么就会走后面的流程。

偏向锁的实现原理

一个线程在第一次进入同步块时,会在对象头和栈帧中的锁记录里存储锁偏向的线程 ID。当下次该线程进入这个同步块时,会去检查锁的 Mark Word 里面是不是放的自己的线程 ID。如果是,表明该线程已经获得了锁,以后该线程在进入和退出同步块时不需要花费 CAS 操作来加锁和解锁(对比来说轻量级锁每次都需要生成锁记录,然后用锁记录替换 markword );如果不是,就代表有另一个线程来竞争这个偏向锁。这个时候会尝试使用 CAS 来替换 Mark Word 里面的线程 ID 为新线程的 ID,这个时候要分两种情况:

成功,表示之前的线程不存在了, Mark Word 里面的线程 ID 为新线程的 ID,锁不会升级,仍然为偏向锁;

失败,表示之前的线程仍然存在,那么暂停之前的线程,设置偏向锁标识为 0,并设置锁标志位为 00,升级为轻量级锁,会按照轻量级锁的方式进行竞争锁。

CAS 是比较并设置的意思,用于在硬件层面上提供原子性操作。在 在某些处理器架构(如x86)中,比较并交换通过指令 CMPXCHG 实现((Compare and Exchange),一种原子指令),通过比较是否和给定的数值一致,如果一致则修改,不一致则不修改。
在这里插入图片描述
图中涉及到了 lock record 指针指向当前堆栈中的最近一个 lock record,是轻量级锁按照先来先服务的模式进行了轻量级锁的加锁。

撤销偏向锁

偏向锁使用了一种等到竞争出现才释放锁的机制,所以当其他线程尝试竞争偏向锁时,持有偏向锁的线程才会释放锁。

偏向锁升级成轻量级锁时,会暂停拥有偏向锁的线程,重置偏向锁标识,这个过程看起来容易,实则开销还是很大的,大概的过程如下:

在一个安全点(在这个时间点上没有字节码正在执行)停止拥有锁的线程。
遍历线程栈,如果存在锁记录的话,需要修复锁记录和 Mark Word,使其变成无锁状态。
唤醒被停止的线程,将当前锁升级成轻量级锁。
所以,如果应用程序里所有的锁通常处于竞争状态,那么偏向锁就会是一种累赘,对于这种情况,我们可以一开始就把偏向锁这个默认功能给关闭。

调用对象的 hashCode 函数时会生成 hashCode,但对于偏向锁 Mark Word 里记录的是线程 id,没地方放哈希值,所以会撤销偏向锁。轻量级锁在锁记录里记录 hashCode,重量级锁会在 Monitor 中记录 hashCode。

调用 wait() 或 notify() 方法会触发偏向锁的撤销,并升级为重量级锁。这是因为 wait() 和 notify() 引入了线程间的竞争和同步机制,而偏向锁无法应对这种场景。

在这里插入图片描述


五、轻量级锁

多个线程在不同时段获取同一把锁,即不存在锁竞争的情况,也就没有线程阻塞。针对这种情况,JVM 采用轻量级锁来避免线程的阻塞与唤醒。

JVM 会为每个线程在当前线程的栈帧中创建用于存储锁记录的空间,我们称为 Displaced Mark Word。如果一个线程获得锁的时候发现是轻量级锁,会把锁的 Mark Word 复制到自己的 Displaced Mark Word 里面。

然后线程尝试用 CAS 将锁的 Mark Word 替换为指向锁记录的指针。如果成功,当前线程获得锁,如果失败,表示 Mark Word 已经被替换成了其他线程的锁记录,说明在与其它线程竞争锁,当前线程就尝试使用自旋来获取锁。

自旋:不断尝试去获取锁,一般用循环来实现。

自旋是需要消耗 CPU 的,如果一直获取不到锁的话,那该线程就一直处在自旋状态,白白浪费 CPU 资源。解决这个问题最简单的办法就是指定自旋的次数,例如让其循环 10 次,如果还没获取到锁就进入阻塞状态。

但是 JDK 采用了更聪明的方式——适应性自旋,简单来说就是线程如果自旋成功了,则下次自旋的次数会更多,如果自旋失败了,则自旋的次数就会减少。

自旋也不是一直进行下去的,如果自旋到一定程度(和 JVM、操作系统相关),依然没有获取到锁,称为自旋失败,那么这个线程会阻塞。同时这个锁就会升级成重量级锁。

轻量级锁的释放

在释放锁时,当前线程会使用 CAS 操作将 Displaced Mark Word 的内容复制回锁的 Mark Word 里面。如果没有发生竞争,那么这个复制的操作会成功。如果有其他线程因为自旋多次导致轻量级锁升级成了重量级锁,那么 CAS 操作会失败,此时会释放锁并唤醒被阻塞的线程。

在这里插入图片描述


六、重量级锁

重量级锁依赖于操作系统的互斥锁(mutex,用于保证任何给定时间内,只有一个线程可以执行某一段特定的代码段) 实现,而操作系统中线程间状态的转换需要相对较长的时间,所以重量级锁效率很低,但被阻塞的线程不会消耗 CPU。

每一个对象都可以当做一个锁,当多个线程同时请求某个对象锁时,对象锁会设置几种状态用来区分请求的线程:

Contention List:所有请求锁的线程将被首先放置到该竞争队列
Entry List:Contention List 中那些有资格成为候选人的线程被移到 Entry List
Wait Set:那些调用 wait 方法被阻塞的线程被放置到 Wait Set
OnDeck:任何时刻最多只能有一个线程正在竞争锁,该线程称为 OnDeck
Owner:获得锁的线程称为 Owner
!Owner:释放锁的线程

当一个线程尝试获得锁时,如果该锁已经被占用,则会将该线程封装成一个ObjectWaiter对象插入到 Contention List 队列的队首,然后调用park 方法挂起当前线程。

当线程释放锁时,会从 Contention List 或 EntryList 中挑选一个线程唤醒,被选中的线程叫做Heir presumptive即假定继承人,假定继承人被唤醒后会尝试获得锁,但synchronized是非公平的,所以假定继承人不一定能获得锁。

这是因为对于重量级锁,如果线程尝试获取锁失败,它会直接进入阻塞状态,等待操作系统的调度。

如果线程获得锁后调用Object.wait方法,则会将线程加入到 WaitSet 中,当被Object.notify唤醒后,会将线程从 WaitSet 移动到 Contention List 或 EntryList 中去。需要注意的是,当调用一个锁对象的wait或notify方法时,如当前锁的状态是偏向锁或轻量级锁则会先膨胀成重量级锁。


七、锁的升级流程

每一个线程在准备获取共享资源时: 第一步,检查 MarkWord 里面是不是放的自己的 ThreadId ,如果是,表示当前线程是处于 “偏向锁” 。

第二步,如果 MarkWord 不是自己的 ThreadId,锁升级,这时候,用 CAS 来执行切换,新的线程根据 MarkWord 里面现有的 ThreadId,通知之前线程暂停,之前线程将 Markword 的内容置为空。

第三步,两个线程都把锁对象的 HashCode 复制到自己新建的用于存储锁的记录空间,接着开始通过 CAS 操作, 把锁对象的 Markword 的内容修改为自己新建的记录空间的地址的方式竞争 MarkWord。

第四步,第三步中成功执行 CAS 的获得资源,失败的则进入自旋 。

第五步,自旋的线程在自旋过程中,成功获得资源(即之前获的资源的线程执行完成并释放了共享资源),则整个状态依然处于 轻量级锁的状态,如果自旋失败 。

第六步,进入重量级锁的状态,这个时候,自旋的线程进行阻塞,等待之前线程执行完成并唤醒自己。


八、sleep / wait / park

sleep

sleep 是 Thread 类的静态方法,它的作用是让当前线程暂停执行一段指定的时间(毫秒或纳秒),到时间后自动恢复,无需外部干预,但暂停期间不会释放持有的锁

wait

wait 是Object类的实例方法,它的作用是让当前线程暂停执行,进入waitSet等待,并释放持有的锁,直到其他线程调用 notify() 或 notifyAll() 唤醒它。
与sleep不同的是它会释放锁,并且需要 synchronized 配合,需要外部唤醒。

park

park 是 LockSupport 类的静态方法,它的作用是暂停线程的执行,直到其他线程调用 unpark() 或线程被中断,暂停期间不会释放持有的锁
它不需要配合 synchronized,也不会释放锁资源,unpark() 会提前提供一个许可证,下次 park 时不会进入阻塞。


九、多把锁相关

多把锁的优势是可以增加并发度,但是如果一个线程需要多把锁就容易发生死锁,例如哲学家就餐问题。死锁属于活跃性问题,除了死锁还有活锁和饥饿两种情况。活锁是两个线程互为对方的结束条件而无法结束,饥饿则是一个线程的优先级太低,始终无法得到CPU的调度,拿不到锁。


十、ReentrantLock

可重入是指同一个线程如果首次获得了这把锁,那么因为它是这把锁的拥有者,因此有权利再次获取这把锁。如果是不可重入锁,那么第二次获得锁时,自己也会被锁挡住。

  1. 可重入锁的等待期间可以被 interrupt 打断。
  2. 获取锁超时会立即失败。
  3. ReentrantLock 默认是不公平的,也支持公平模式。
  4. ReentrantLock 支持多个条件变量(多间休息室)。
  5. 内部维护持有计数,记录锁被同一线程获取的次数,确保完全释放。

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

upload-labs详解(13-20)文件上传分析

目录 upload-labs-env upload-labs-env第十三关 文件包含漏洞 代码 测试 上传一个.jpg图片 上传一个.png文件 上传一个.gif图片 upload-labs-env第十四关 代码 思路 upload-labs-env第十五关 代码 思路 upload-labs-env第十六关 代码 思路 测试 上传gif格式…

探索高性能AI识别和边缘计算 | NVIDIA Jetson Orin Nano 8GB 开发套件的全面测评

随着边缘计算和人工智能技术的迅速发展,性能强大的嵌入式AI开发板成为开发者和企业关注的焦点。NVIDIA近期推出的Jetson Orin Nano 8GB开发套件,凭借其40 TOPS算力、高效的Ampere架构GPU以及出色的边缘AI能力,引起了广泛关注。本文将从配置性…

字典树(trie树)详解

【本文概要】本文主要介绍了字典树的概念,字典树的一般算法,包括初始化,插入,查找等,最后举了比较典型的案例来辅助理解字典树这种特殊的数据结构。 1、什么是字典树 字典树,是一种特殊的树状数据结构&…

从CL1看生物计算机的创新突破与发展前景:技术、应用与挑战的多维度剖析

一、引言 1.1 研究背景与意义 随着科技的飞速发展,计算机技术已经成为推动现代社会进步的核心力量之一。从最初的电子管计算机到如今的大规模集成电路计算机,计算机的性能得到了极大的提升,应用领域也不断拓展。然而,传统计算机…

小兔鲜Vue3

counterStore里面包含着对象返回的东西。 getters就是conputer git initgit add .git commit -m " " jsconfig进行路径提示。vite.config.js进行实际路径转化。 第一个文件做好就是一个axios实例了,可以直接调用方法。 在第二个文件是实例.get 写好路…

驱动 AI 边缘计算新时代!高性能 i.MX 95 应用平台引领未来

智慧浪潮崛起:AI与边缘计算的时代 正悄然深植于我们的日常生活之中,无论是火热的 ChatGPT 与 DeepSeek 语言模型,亦或是 Meta 智能眼镜,AI 技术已经无形地影响着我们的生活。这股变革浪潮并未停歇,而是进一步催生了更高…

STM32之软件SPI

SPI传输更快,最大可达80MHz,而I2C最大只有3.4MHz。输入输出是分开的,可以同时输出输入。是同步全双工。仅支持一主多从。SS是从机选择线。每个从机一根。SPI无应答机制的设计。 注意:所有设备需要共地,时钟线主机输出&…

深度学习系列79:Text2sql调研

参考 https://github.com/topics/text-to-sql 这里是一些资源:https://github.com/eosphoros-ai/Awesome-Text2SQL/blob/main/README.zh.md 这里是综述文章:https://zhuanlan.zhihu.com/p/647249972 1. 数据集 Spider: 一个跨域的复杂text2sql数据集&a…

【Unity】 HTFramework框架(六十一)Project窗口文件夹锁定器

更新日期:2025年3月7日。 Github源码:[点我获取源码] Gitee源码:[点我获取源码] 索引 Project窗口文件夹锁定器框架文件夹锁定自定义文件夹锁定限制条件 Project窗口文件夹锁定器 在Project窗口中,文件夹锁定器能够为任何文件夹加…

nginx服务器实现上传文件功能_使用nginx-upload-module模块

目录 conf文件内容如下html文件内容如下上传文件功能展示 conf文件内容如下 #user nobody; worker_processes 1;error_log /usr/logs/error.log; #error_log /usr/logs/error.log notice; #error_log /usr/logs/error.log info;#pid /usr/logs/nginx.pid;even…

基于云的内容中台核心优势是什么?

弹性云架构赋能资源整合 现代企业通过弹性云架构实现多源数据资源的深度整合,其动态扩展能力可自动适配业务流量波动。基于分布式存储与容器化部署,系统能够无缝对接CRM、ERP等企业软件集成,实现跨平台数据实时同步。值得注意的是&#xff0…

*图论基础(5)

持续更新... 1.图的基本概念 不写了,网上有好多资料ovo 2.图的存储和遍历 2.1存储: 3.最小生成树 3.2Kruskal算法 4.拓扑排序 拓扑排序的⽬标是将有向⽆环图中的所有结点排序,使得排在前⾯的结点不能依赖于排在后⾯的结 点。在课程问题中…

DeepSeek 助力 Vue3 开发:打造丝滑的表格(Table)示例3: 行选择

前言:哈喽,大家好,今天给大家分享一篇文章!并提供具体代码帮助大家深入理解,彻底掌握!创作不易,如果能帮助到大家或者给大家一些灵感和启发,欢迎收藏关注哦 💕 目录 Deep…

DevSecOps CI/CD 管道中数字供应链安全的集成策略

前言: 在敏捷开发的模式下,应用程序会通过 DevSecOps 的敏捷软件开发生命周期(SDLC)范式进行开发,并使用持续集成/持续交付(CI/CD)管道的流程。 然而,在软件开发、供应和交付运营中…

JmeterHttp请求头管理出现Unsupported Media Type问题解决

JmeterHttp请求头管理出现Unsupported Media Type问题解决 大多数的app与pc端压测的时候都会出现这种情况 当我们在jemter测试当中当中遇见Unsupported Media Type,有一种可能就是我们请求的网页的content-Type的类型与我们测试的时候的类型不一致 解决方法 可以添…

STM32 子设备通过CAN发送数据到主设备

采集ADC、GPS经纬坐标、温湿度数据、大气压数据通过CAN方式发送给主设备端,帧ID按照如下定义: 我尼玛一个标准帧ID位数据是11位,扩展帧才是111829位,它说最开头的是四位是真类型,并给我如下解释: 它把帧的定…

基于深度学习的青花瓷图像检索系统开发与实现

目录 1.研究背景与目的 1.1课题背景 1.2研究目的 二、调研资料情况 2.1图像分割研究现状 2.2图像检索调研 2.2.1选择深度学习进行检索的原因及优势 2.2.2基于深度学习的图像检索技术的发展 2.2.3基于深度学习的图像检索的研究重点 2.3基于深度学习的图像检索方法调研 …

FreeRTOS学习(七):通过实例深入理解栈的作用(二)

FreeRTOS学习(七):通过实例深入理解栈的作用(二) 文章目录 FreeRTOS学习(七):通过实例深入理解栈的作用(二)前言一、栈的深度局部变量调用深度 总结 前言 看…

[傻瓜式教学]如何将MathType公式编辑器内嵌到WPS工具栏中

[傻瓜式教学]如何将MathType公式编辑器内嵌到WPS工具栏中 将MathType公式编辑器内嵌到WPS工具栏中 下载好所需文件 我用夸克网盘分享了「mathtype安装教程超简单易上手.zip」,点击链接即可保存。打开「夸克APP」 链接:https://pan.quark.cn/s/4726c684…

网络安全整改措施复函

🍅 点击文末小卡片 ,免费获取网络安全全套资料,资料在手,涨薪更快 以计算机安全的主要因素为突破口,重点防范各种不利于计算机网络正常运行的措施,从不同角度全面了解影响计算机网络安全的情况,…