当前位置: 首页 > news >正文

Synchronized重量级锁原理和实战(五)

在JVM中,每个对象都关联这一个监视器,这里的对象包含可Object实例和Class实例.监视器是一个同步工具,相当于一个凭证,拿到这个凭证就可以进入临界区执行操作,没有拿到凭证就只能阻塞等待.重量级锁通过监视器的方式保证了任何时间内只允许一个线程通过监视器保护的临界区代码.

重量级锁的核心原理

JVM每个对象都有一个监视器,监视器随着对象一起创建 销毁.本质上监视器是一种同步工具,也可以说是一种同步机制.

1:同步.监视器所保护的临界区代码都是互斥执行的.一个监视器一个凭证,任何一个线程执行临界区代码都需要获取凭证,执行完了释放许可.

2:协作.监视器提供Signal机制,允许正在持有许可的线程释放凭证进入阻塞等待状态,等待其他线程发送Signal去唤醒.其他拥有凭证的线程可以唤醒正在阻塞等待的线程,让它可以重新获得凭证执行临界区代码.

在虚拟机中,监视器是由C++类ObjectMonitor实现的.(我对C++不是很熟,就简单介绍下,有个印象知道如何实现,如果对技术很感兴趣,可以去学学C++)

ObjectMonitor类中关键的属性是Owner(_owner) WaitSet(_WaitSet) Cxq(_cxq) EntrlList(_EntrlList).好好品味,显示锁实现原理和这个相同.

WaitSet Cxq EntrlList说明

Cxq:竞争队列,所有请求锁的线程首先被放到竞争队列里.

EntrlList:Cxq中那些有资格成为候选资源的线程被移到EntrlList中.

WaitSet:某个拥有ObjectMonitor的线程在调用Object.wait()方法之后被阻塞,然后该线程被放置在WaitSet列表中.

ObjectMonitor内部抢锁流程

1:Cxq

Cxq并不是一个真正的队列,只是一个虚拟队列,原因在于Cxq是由Node及其next指针逻辑构成,并不存在一个队列数据结构,(我自己的理解是引用,就好比1引用2,2引用3,以此类推).

每次新加入Node会在Cxq的队头进行,通过CAS改变第一个节点的指针为新增节点.同时新增节点的next指针指向后续节点.从Cxq取元素时,会从队尾获取.可以看出来,Cxq是一个无锁结构.

因为只有Owner线程才能从队尾获取元素,所以线程出队没有竞争,也避免了ABA问题.还有线程在进入Cxq之前,会通过CAS操作进行一次抢锁,获取不到才会进入队列,所以重量级锁是一个非公平锁.

2:EntrlList

EntrlList与Cxq在逻辑上都属于等待队列.Cxq会被线程并发访问为了降低对Cxq队尾的竞争,而建立了EntrlList,在Owner线程释放锁的时候,JVM会从Cxq中迁移线程到EntrlList,并会指定EntrlList中的某个线程(一般为头线程)为OnDeck Thread(Ready Thread).EntrlList里的线程作为候选者线程存在.

3:OnDeck Thread 与 Owner Thread

JVM不直接把锁传递给Owner Thread,而是把锁竞争的权利交给OnDeck Thread,OnDeck需要重新竞争锁,这样虽然牺牲了一定的公平性,但是提高了吞吐量.在JVM中也把这种行为叫做竞争切换.

OnDeck Thread线程获取到锁后会变为Owner Thread,无法获取锁的OnDeck Thread依然会留在EntrlList中,考虑到公平性,OnDeck Thread在队列中的位置不会变.

在OnDeck Thread成为Owner Thread的过程中还有一个不公平的事情,就是后来新抢锁的线程有可能直接获取锁成为WaitSet.

4:WaitSet

如果Owner Thread调用了Object.wait()方法之后就会进入WaitSet队列.直到某个时刻调用Object.notify()方法或者Object.notifyAll()唤醒,线程会重新进入EntrlList队列.

重量级锁开销

处于ContentionList EntrlList WaitSet中的线程都处于阻塞状态.线程的阻塞和唤醒都需要操作系统来帮忙.Linux内核下采用pthread_mutex_lock系统调用实现,进程要从用户态切换到内核态.

Linux系统的体系分为用户态和内核态.

Linux系统的内核是一组特殊的软件程序,负责控制计算机硬件资源.例如协调CPU资源,分配内存资源,并且提供稳定的环境供程序运行.应用程序的活动空间为用户空间,应用程序的执行必须依托于内核提供的资源,包括CPU资源 存储资源 IO资源等.

用户态与内核态有各自专用的内存空间 专用的寄存器等.进程从用户态切换至内核态需要传递许多变量 参数给内核,内核也需要保护好用户态在切换时的一些寄存器值 变量等.以便内核态调用结束后可以切换回用户态继续工作.

用户态的进程能够访问的资源受到了极大的控制,而运行在内核态的进程可以为所欲为.一个进程可以运行在用户态也可以运行在内核态,它们之间肯定有切换的方式.

用户态切换内核态的方式

1:硬件中断.硬件中断也称为外设中断,当外设完成用户请求时,会向CPU发送中断信号.

2:系统调用.其实系统调用本身就是中断,只不过是软件中断,与硬件中断不同.

3:异常.如果当前进程运行在用户态,这时发生了异常事件(例如缺页异常),就会触发切换.

用户态是应用程序运行的空间,为了能访问到内核管理的资源(例如CPU 内存 IO),可以通过内核态所提供的的访问接口实现,这些接口叫做系统调用.pthread_mutex_lock系统调用是内核态为用户态提供的Linux内核态下互斥锁的访问机制,所以使用pthread_mutex_lock系统调用时,进程需要从用户态切换到内核态,这种切换要消耗很多的时间,有可能比用户执行代码的时间还长.

重量级锁演示

public class HeavyWeightLockTest {static final int MAX_TURN = 1000;public static void main(String[] args) throws InterruptedException {System.out.println(VM.current().details());//JVM偏向锁.Thread.sleep(5000);ObjectLock objectLock = new ObjectLock();//抢锁前状态.System.out.println("抢锁前objectLock状态:");objectLock.printObjectStruct();Thread.sleep(5000);CountDownLatch latch = new CountDownLatch(3);Runnable runnable = () -> {for (int i = 0; i < MAX_TURN; i++) {synchronized (objectLock) {objectLock.increase();if (i == 0) {System.out.println("第一个线程抢锁,lock的状态为:");objectLock.printObjectStruct();}}}latch.countDown();for (int j = 0; ; j++) {LockSupport.parkNanos(10);}};new Thread(runnable).start();LockSupport.parkNanos(2000);Runnable lightWeightRunnable = () -> {for (int i = 0; i < MAX_TURN; i++) {synchronized (objectLock) {objectLock.increase();if (i == 0) {System.out.println(Thread.currentThread().getName()+ "占有锁,lock的状态为:");objectLock.printObjectStruct();}LockSupport.parkNanos(10);}}latch.countDown();};//启动两个线程开始抢锁.new Thread(lightWeightRunnable, "抢锁线程-1").start();Thread.sleep(5000);new Thread(lightWeightRunnable, "抢锁线程-2").start();latch.await();LockSupport.parkNanos(2000);System.out.println("释放锁,lock的状态为:");objectLock.printObjectStruct();}
}

可以看出lock标记位为偏向锁状态,还没有偏向线程.

 

可以看出lock标记位101为偏向状态,并且有了偏向线程.

 

可以看出lock标记位000已经不是偏向锁,升级为了轻量级锁.

 

可以看出lock标记位变为了010成为了重量级锁. 

我一直在努力,被质疑,被嘲讽.在沮丧,还是会在第二天早晨一如既往的去战斗.我坚信我会成功.

如果大家喜欢我的分享的话,可以关注下我的微信公众号

心有九月星辰

相关文章:

Synchronized重量级锁原理和实战(五)

在JVM中,每个对象都关联这一个监视器,这里的对象包含可Object实例和Class实例.监视器是一个同步工具,相当于一个凭证,拿到这个凭证就可以进入临界区执行操作,没有拿到凭证就只能阻塞等待.重量级锁通过监视器的方式保证了任何时间内只允许一个线程通过监视器保护的临界区代码. …...

linux常用网络工具汇总三

linux常用网络工具汇总 6. 抓包工具6.1 wireshark安装界面介绍使用过滤器TCP协议示例关于wireshark的缺点 6.2 tcpdump命令格式关键字使用关于tcpdump的缺点 6.3 fiddler6.4 burpsuite 6. 抓包工具 6.1 wireshark Wireshark&#xff08;前称Ethereal&#xff09;是一个网络封…...

Linux中nano编辑器详解

nano 是一个简单的文本编辑器&#xff0c;通常预装在大多数 Linux 发行版中。它非常适合初学者使用&#xff0c;因为它有一个用户友好的界面和易于理解的命令集。下面是对 nano 编辑器的详细说明。 启动 nano 要启动 nano 并打开一个文件进行编辑&#xff0c;你可以在终端中输…...

26-vector arraylist和linkedlist的区别

‌Vector, ArrayList, 和 LinkedList 是Java中常见的三种列表实现&#xff0c;它们各自具有不同的特点和适用场景。‌ ‌同步性与线程安全‌&#xff1a; ‌Vector‌ 是同步的&#xff0c;即线程安全的&#xff0c;它的所有方法都是同步的&#xff0c;可以由两个线程安全地访问…...

20-redis穿透击穿雪崩

Redis中的缓存穿透、‌缓存击穿和缓存雪崩是三种常见的缓存问题&#xff1a;‌ 缓存穿透&#xff1a;‌指缓存和数据库中都没有的数据&#xff0c;‌但用户还是源源不断地发起请求&#xff0c;‌导致每次请求都会直接访问数据库&#xff0c;‌从而可能压垮数据库。‌缓存击穿&…...

Docker使用教程

Docker 名词解释 镜像&#xff08;image&#xff09;&#xff1a;Docker镜像就是一个模板&#xff0c;可以通过这个模板来创建容器服务。容器&#xff08;container&#xff09;&#xff1a;Docker利用容器技术&#xff0c;独立运行一个或者一组应用&#xff0c;通过镜像创建…...

poi-tl循环放图片+文字说明

这几天有个任务&#xff0c;服务端导出word要求从数据库取到多张图片&#xff0c;然后输出到word中&#xff0c;并且说明一共几张&#xff0c;当前是第几张。 网上翻了很久也没有找到示例&#xff0c;不过最终难题还是得到了攻克。 因为之前的代码是有一个导出的map&#xff0c…...

数据结构之树的存储结构

一、顺序存储结构 顺序存储结构通常用于表示完全二叉树。在这种存储方式中&#xff0c;树中的节点被存储在一个连续的数组中。对于完全二叉树&#xff0c;如果父节点的索引是i&#xff08;假设从0开始计数&#xff09;&#xff0c;那么它的左子节点的索引是2i1&#xff0c;右子…...

Zotero 常用插件介绍

1. Zotero 插件安装方法 下载以 .xpi 结尾的插件&#xff1b;打开 Zotero → 工具 → 插件 → 右上小齿轮图标 → Install Add-on From File ... → 选择下载好的 .xpi 插件安装 → 重启 Zotero 2. 常用插件介绍 2.1. Scholaread - 靠岸学术 Zotero 英文文献相关插件&#xf…...

WebSocket协议解析

文章目录 一、HTTP协议与HTTPS协议1.HTTP协议的用处2.HTTP协议的特点3.HTTP协议的工作流程4.HTTPS协议的用处5.HTTPS协议的特点6.HTTPS协议的工作流程 二、WebSocket协议出现的原因1. 传统的HTTP请求-响应模型2. 轮询&#xff08;Polling&#xff09;3. 长轮询&#xff08;Long…...

ES6 (一)——ES6 简介及环境搭建

目录 简介 环境搭建 可以在 Node.js 环境中运行 ES6 webpack 入口 (entry) loader 插件 (plugins) 利用 webpack 搭建应用 gulp 如何使用&#xff1f; 简介 ES6&#xff0c; 全称 ECMAScript 6.0 &#xff0c;是 JavaScript 的下一个版本标准&#xff0c;2015.06 发版…...

HarmonyOS开发案例:列表场景实例-TaskPool

介绍 本实例通过列表场景实例讲解&#xff0c;介绍在TaskPool线程中操作关系型数据库的方法&#xff0c;涵盖单条插入、批量插入、删除和查询操作。 效果图预览 使用说明 进入页面有insert(单条数据插入)、batch insert(批量数据插入)、query(查询操作)三个按钮&#xff0c;…...

谷歌浏览器如何隐藏书签

谷歌浏览器的书签栏是一个极为方便的功能&#xff0c;它能够帮助用户快速访问自己频繁使用的网页。然而&#xff0c;有些时候为了保护个人隐私或使浏览界面更为简洁&#xff0c;我们可能需要隐藏书签栏。接下来就为大家分享如何隐藏谷歌浏览器的书签栏&#xff0c;一起来看看吧…...

SQL - 视图

我们可以把查询或子查询存到视图里&#xff0c;视图的作用就像一张虚拟表&#xff0c;再次查询时&#xff0c;就不需要再写一次复杂的查询。创建视图 create view 视图名 as (查询); create or replace view clients_balance as (查询); create or replace view clients_balanc…...

centos7环境升级默认的gcc 4.8.5到gcc 8.2.0, 并且升级glibc到glibc 2.28

这里写目录标题 makegccglibc make #下载 wget http://ftp.gnu.org/gnu/make/make-4.2.tar.gz tar -xf make-4.2.tar.gz cd make-4.2 ./configure make -j4 make install mv /usr/bin/make /usr/bin/make_bak cp ./make /usr/bin/make -v GNU Make 4.2 Built for x86_64-pc-li…...

FastHTML:使用 Python 彻底改变 Web 开发

什么是 FastHTML&#xff1f;&#x1f310; FastHTML 是一个现代 Python Web 应用程序框架&#xff0c;其真正目的是让 Python 开发人员轻松进行 Web 开发。它大大减少了对 JavaScript 和 CSS 构建交互式和可扩展 Web 应用程序的依赖。FastHTML 通过使用 Python 对象来表示 HTM…...

快速排序的深入优化探讨

快排性能的关键点分析 决定快排性能的关键点是每次单趟排序后&#xff0c;key对数组的分割&#xff0c;如果每次选key基本⼆分居中&#xff0c;那么快排的递归树就是颗均匀的满⼆叉树&#xff0c;性能最佳。但是实践中虽然不可能每次都是⼆分居中&#xff0c;但是性能也还是可…...

c语言杂谈系列:模拟虚函数

从整体来看&#xff0c;笔者的做法与之前的模拟多态十分相似&#xff0c;毕竟c多态的实现与虚函数密切相关 废话少说&#xff0c;see my code&#xff1a; kernel.c#include "kernel.h" #include <stdio.h>void shape_draw(struct shape_t* obj) {/* Call dr…...

短视频推广App不再难!Xinstall来帮忙

在短视频风靡的今天&#xff0c;如何利用这一热门媒介有效推广App&#xff0c;成为了许多推广者关注的焦点。而Xinstall&#xff0c;作为国内专业的App全渠道统计服务商&#xff0c;正是你解决这一难题的得力助手。 首先&#xff0c;Xinstall在数据维度上的优势无可比拟。它能…...

打靶记录13——doubletrouble

靶机&#xff1a; https://www.vulnhub.com/entry/doubletrouble-1,743/ 难度&#xff1a; 中 目标&#xff1a; 取得两台靶机 root 权限 涉及攻击方法&#xff1a; 主机发现端口扫描Web信息收集开源CMS漏洞利用隐写术密码爆破GTFObins提权SQL盲注脏牛提权 学习记录&am…...

2025 后端自学UNIAPP【项目实战:旅游项目】6、我的收藏页面

代码框架视图 1、先添加一个获取收藏景点的列表请求 【在文件my_api.js文件中添加】 // 引入公共的请求封装 import http from ./my_http.js// 登录接口&#xff08;适配服务端返回 Token&#xff09; export const login async (code, avatar) > {const res await http…...

【C语言练习】080. 使用C语言实现简单的数据库操作

080. 使用C语言实现简单的数据库操作 080. 使用C语言实现简单的数据库操作使用原生APIODBC接口第三方库ORM框架文件模拟1. 安装SQLite2. 示例代码:使用SQLite创建数据库、表和插入数据3. 编译和运行4. 示例运行输出:5. 注意事项6. 总结080. 使用C语言实现简单的数据库操作 在…...

华为云Flexus+DeepSeek征文|DeepSeek-V3/R1 商用服务开通全流程与本地部署搭建

华为云FlexusDeepSeek征文&#xff5c;DeepSeek-V3/R1 商用服务开通全流程与本地部署搭建 前言 如今大模型其性能出色&#xff0c;华为云 ModelArts Studio_MaaS大模型即服务平台华为云内置了大模型&#xff0c;能助力我们轻松驾驭 DeepSeek-V3/R1&#xff0c;本文中将分享如何…...

STM32HAL库USART源代码解析及应用

STM32HAL库USART源代码解析 前言STM32CubeIDE配置串口USART和UART的选择使用模式参数设置GPIO配置DMA配置中断配置硬件流控制使能生成代码解析和使用方法串口初始化__UART_HandleTypeDef结构体浅析HAL库代码实际使用方法使用轮询方式发送使用轮询方式接收使用中断方式发送使用中…...

GAN模式奔溃的探讨论文综述(一)

简介 简介:今天带来一篇关于GAN的,对于模式奔溃的一个探讨的一个问题,帮助大家更好的解决训练中遇到的一个难题。 论文题目:An in-depth review and analysis of mode collapse in GAN 期刊:Machine Learning 链接:...

欢乐熊大话蓝牙知识17:多连接 BLE 怎么设计服务不会乱?分层思维来救场!

多连接 BLE 怎么设计服务不会乱&#xff1f;分层思维来救场&#xff01; 作者按&#xff1a; 你是不是也遇到过 BLE 多连接时&#xff0c;调试现场像网吧“掉线风暴”&#xff1f; 温度传感器连上了&#xff0c;心率带丢了&#xff1b;一边 OTA 更新&#xff0c;一边通知卡壳。…...

P10909 [蓝桥杯 2024 国 B] 立定跳远

# P10909 [蓝桥杯 2024 国 B] 立定跳远 ## 题目描述 在运动会上&#xff0c;小明从数轴的原点开始向正方向立定跳远。项目设置了 $n$ 个检查点 $a_1, a_2, \cdots , a_n$ 且 $a_i \ge a_{i−1} > 0$。小明必须先后跳跃到每个检查点上且只能跳跃到检查点上。同时&#xff0…...

【汇编逆向系列】六、函数调用包含多个参数之多个整型-参数压栈顺序,rcx,rdx,r8,r9寄存器

从本章节开始&#xff0c;进入到函数有多个参数的情况&#xff0c;前面几个章节中介绍了整型和浮点型使用了不同的寄存器在进行函数传参&#xff0c;ECX是整型的第一个参数的寄存器&#xff0c;那么多个参数的情况下函数如何传参&#xff0c;下面展开介绍参数为整型时候的几种情…...

AT模式下的全局锁冲突如何解决?

一、全局锁冲突解决方案 1. 业务层重试机制&#xff08;推荐方案&#xff09; Service public class OrderService {GlobalTransactionalRetryable(maxAttempts 3, backoff Backoff(delay 100))public void createOrder(OrderDTO order) {// 库存扣减&#xff08;自动加全…...

智能体革命:企业如何构建自主决策的AI代理?

OpenAI智能代理构建实用指南详解 随着大型语言模型&#xff08;LLM&#xff09;在推理、多模态理解和工具调用能力上的进步&#xff0c;智能代理&#xff08;Agents&#xff09;成为自动化领域的新突破。与传统软件仅帮助用户自动化流程不同&#xff0c;智能代理能够自主执行工…...