深入理解多线程(四)— Moniter的实现原理

article/2025/9/15 1:27:01

深入理解多线程(四)— Moniter的实现原理

在深入理解多线程(一)—Synchronized的实现原理中介绍过关于Synchronize的实现原理,无论是同步方法还是同步代码块,无论是ACC_SYNCHRONIZED还是monitorenter、monitorexit都是基于Monitor实现的,接下来我们就详细介绍下什么是Monitor。

1. 操作系统中的monitors

管程(monitors)在操作系统中是很重要的概念,下面是管程的概念:
管程 (英语:Monitors,也称为监视器) 是一种程序结构,结构内的多个子程序(对象或模块)形成的多个工作线程互斥访问共享资源。这些共享资源一般是硬件设备或一群变量。管程实现了在一个时间点,最多只有一个线程在执行管程的某个子程序。与那些通过修改数据结构实现互斥访问的并发程序设计相比,管程实现很大程度上简化了程序设计。 管程提供了一种机制,线程可以临时放弃互斥访问,等待某些条件得到满足后,重新获得执行权恢复它的互斥访问。

2. java中的monitor

monitor在java同步机制中使用。在多线程访问共享资源的时候,经常会带来可见性和原子性的安全问题。为了解决这类线程安全的问题,Java提供了同步机制、互斥锁机制,这个机制保证了在同一时刻只有一个线程能访问共享资源。这个机制的保障来源于监视锁Monitor,每个对象都拥有自己的监视锁Monitor。
先来举个例子,然后我们在上源码。我们可以把监视器理解为包含一个特殊的房间的建筑物,这个特殊房间同一时刻只能有一个客人(线程)。这个房间中包含了一些数据和代码。如下图所示:
这里写图片描述
如果一个顾客想要进入这个特殊的房间,他首先需要在走廊(Entry Set)排队等待。调度器将基于某个标准(比如 FIFO)来选择排队的客户进入房间。如果,因为某些原因,该客户客户暂时因为其他事情无法脱身(线程被挂起),那么他将被送到另外一间专门用来等待的房间(Wait Set),这个房间的可以可以在稍后再次进入那件特殊的房间。如上面所说,这个建筑屋中一共有三个场所。如下图:
这里写图片描述
总之,监视器是一个用来监视这些线程进入特殊的房间的。他的义务是保证(同一时间)只有一个线程可以访问被保护的数据和代码。
Monitor其实是一种同步工具,也可以说是一种同步机制,它通常被描述为一个对象,主要特点是:
(1)对象的所有方法都被“互斥”的执行。好比一个Monitor只有一个运行“许可”,任一个线程进入任何一个方法都需要获得这个“许可”,离开时把许可归还。
(2)通常提供singnal机制:允许正持有“许可”的线程暂时放弃“许可”,等待某个谓词成真(条件变量),而条件成立后,当前进程可以“通知”正在等待这个条件变量的线程,让他可以重新去获得运行许可。

3. 监视器的实现

接下来我们首先看下在monitor在Java虚拟机(HotSpot)中的实现,其实现是基于C++的,由ObjectMonitor实现的,其主要数据结构如下:

  ObjectMonitor() {_header       = NULL;_count        = 0;_waiters      = 0,_recursions   = 0;_object       = NULL;_owner        = NULL;_WaitSet      = NULL;_WaitSetLock  = 0 ;_Responsible  = NULL ;_succ         = NULL ;_cxq          = NULL ;FreeNext      = NULL ;_EntryList    = NULL ;_SpinFreq     = 0 ;_SpinClock    = 0 ;OwnerIsThread = 0 ;}

ObjectMonitor.hpp源码地址:
在上面的源码我们可以看到ObjectMonitor中有几个关键属性:
- _owner:指向持有ObjectMonitor对象的线程
- _WaitSet:存放处于wait状态的线程队列
- _EntryList:存放处于等待锁block状态的线程队列
- _recursions:锁的重入次数
- _count:用来记录该线程获取锁的次数
当多个线程同时访问一段同步代码时,首先会进入_EntryList队列中,当某个线程获取到对象的monitor后进入_Owner区域并把monitor中的_owner变量设置为当前线程,同时monitor中的计数器_count加1。即获得对象锁。
若持有monitor的线程调用wait()方法,将释放当前持有的monitor,_owner变量恢复为null,_count自减1,同时该线程进入_WaitSet集合中等待被唤醒。若当前线程执行完毕也将释放monitor(锁)并复位变量的值,以便其他线程进入获取monitor(锁)。如下图所示:
这里写图片描述
在ObjectMonitor类中提供了几个方法:

3.1 获得锁方法

void ATTR ObjectMonitor::enter(TRAPS) {Thread * const Self = THREAD ;void * cur ;//通过CAS尝试把monitor的`_owner`字段设置为当前线程cur = Atomic::cmpxchg_ptr (Self, &_owner, NULL) ;//获取锁失败if (cur == NULL) {         assert (_recursions == 0   , "invariant") ;assert (_owner      == Self, "invariant") ;// CONSIDER: set or assert OwnerIsThread == 1return ;}// 如果旧值和当前线程一样,说明当前线程已经持有锁,此次为重入,_recursions自增,并获得锁。if (cur == Self) { // TODO-FIXME: check for integer overflow!  BUGID 6557169._recursions ++ ;return ;}// 如果当前线程是第一次进入该monitor,设置_recursions为1,_owner为当前线程if (Self->is_lock_owned ((address)cur)) { assert (_recursions == 0, "internal state error");_recursions = 1 ;// Commute owner from a thread-specific on-stack BasicLockObject address to// a full-fledged "Thread *"._owner = Self ;OwnerIsThread = 1 ;return ;}// 省略部分代码。// 通过自旋执行ObjectMonitor::EnterI方法等待锁的释放for (;;) {jt->set_suspend_equivalent();// cleared by handle_special_suspend_equivalent_condition()// or java_suspend_self()EnterI (THREAD) ;if (!ExitSuspendEquivalent(jt)) break ;//// We have acquired the contended monitor, but while we were// waiting another thread suspended us. We don't want to enter// the monitor while suspended because that would surprise the// thread that suspended us.//_recursions = 0 ;_succ = NULL ;exit (Self) ;jt->java_suspend_self();
}
}

获取锁的执行流程如下图所示:
这里写图片描述

3.2 释放锁

void ATTR ObjectMonitor::exit(TRAPS) {Thread * Self = THREAD ;//如果当前线程不是Monitor的所有者if (THREAD != _owner) { if (THREAD->is_lock_owned((address) _owner)) { // // Transmute _owner from a BasicLock pointer to a Thread address.// We don't need to hold _mutex for this transition.// Non-null to Non-null is safe as long as all readers can// tolerate either flavor.assert (_recursions == 0, "invariant") ;_owner = THREAD ;_recursions = 0 ;OwnerIsThread = 1 ;} else {// NOTE: we need to handle unbalanced monitor enter/exit// in native code by throwing an exception.// TODO: Throw an IllegalMonitorStateException ?TEVENT (Exit - Throw IMSX) ;assert(false, "Non-balanced monitor enter/exit!");if (false) {THROW(vmSymbols::java_lang_IllegalMonitorStateException());}return;}}// 如果_recursions次数不为0.自减if (_recursions != 0) {_recursions--;        // this is simple recursive enterTEVENT (Inflated exit - recursive) ;return ;}//省略部分代码,根据不同的策略(由QMode指定),从cxq或EntryList中获取头节点,通过ObjectMonitor::ExitEpilog方法唤醒该节点封装的线程,唤醒操作最终由unpark完成。

这里写图片描述
除了enter和exit方法以外,objectMonitor.cpp中还有几个方法,如下:

void      wait(jlong millis, bool interruptable, TRAPS);
void      notify(TRAPS);
void      notifyAll(TRAPS);

上面介绍的就是HotSpot虚拟机中Moniter的的加锁以及解锁的原理。
通过这篇文章我们知道了sychronized加锁的时候,会调用objectMonitor的enter方法,解锁的时候会调用exit方法。事实上,只有在JDK1.6之前,synchronized的实现才会直接调用ObjectMonitor的enter和exit,这种锁被称之为重量级锁。为什么说这种方式操作锁很重呢?
Java的线程是映射到操作系统原生线程之上的,如果要阻塞或唤醒一个线程就需要操作系统的帮忙,这就要从用户态转换到核心态,因此状态转换需要花费很多的处理器时间,对于代码简单的同步块(如被synchronized修饰的get 或set方法)状态转换消耗的时间有可能比用户代码执行的时间还要长,所以说synchronized是java语言中一个重量级的操纵。
所以,在JDK1.6中出现对锁进行了很多的优化,进而出现轻量级锁,偏向锁,锁消除,适应性自旋锁,锁粗化(自旋锁在1.4就有 只不过默认的是关闭的,jdk1.6是默认开启的),这些操作都是为了在线程之间更高效的共享数据 ,解决竞争问题。后面的文章会继续介绍这几种锁以及他们之间的关系。


http://chatgpt.dhexx.cn/article/tw1nLU5D.shtml

相关文章

锁机制初探(五)Moniter的实现原理

在深入理解多线程(一)——Synchronized的实现原理中介绍过关于Synchronize的实现原理,无论是同步方法还是同步代码块,无论是ACC_SYNCHRONIZED还是monitorenter、monitorexit都是基于Monitor实现的,那么这篇来介绍下什么…

Moniter

了解这个Moniter的实现原理之前,可以说大家已经初步了解了synchronized的底层原理了。无论是同步方法还是同步代码块,无论是ACC_SYNCHRONIZED还是monitorenter、monitorexit都是基于Monitor实现的。 那我们就简单了解下什么Monitor吧!&#…

java什么是monitor和Monitor监视器锁、对象布局

文章目录 Monitor监视器锁什么是moniter对象布局 Monitor监视器锁 每个同步对象都有一个自己的Monitor(监视器锁),加锁过程如下图所示: 任何一个对象都有一个Monitor与之关联,当且一个Monitor被持有后,它将处于锁定状态。Synchro…

Dense Teacher

“从稀疏到密集”的范式使SSOD的流程复杂化,同时忽略了强大的直接、密集的教师监督 - 最新半监督检测框架 论文地址:https://arxiv.org/pdf/2207.05536.pdf Mean-Teacher (MT) 方案在半监督目标检测 (SSOD) 中被广泛采用。在MT中,由教师的最…

Sequential模型、Flatten层、Dense层

Sequential模型 顺序模型 核心操作是添加layers,有两种方法 第一种:通过add()添加 model Sequential() model.add(tf.keras.layers.Dense(10,input_shape(1,),activationrelu))#10表示输出数据的维度,后面表示输入的形状,激活函数为relu model.add(tf…

【Python-Keras】keras.layers.Dense层的解析与使用

1 Dense解析 keras.layers.Dense(units, activationNone, use_biasTrue, kernel_initializerglorot_uniform, bias_initializerzeros, kernel_regularizerNone, bias_regularizerNone, activity_regularizerNone, kernel_constraintNone, bias_constraintNone)实现神经网络里的…

tf.layers.dense()的用法

dense :全连接层 相当于添加一个层 函数如下: tf.layers.dense( inputs, units, activationNone, use_biasTrue, kernel_initializerNone, ##卷积核的初始化器 bias_initializertf.zeros_initializer(), ##偏置项的初始化器,默认初始化为…

DenseNet与ResNet

ResNet(深度残差网络) 深度残差网络 DenseNet 采用密集连接机制,即互相连接所有的层,每个层都会与前面所有层在channel维度上连接在一起,实现特征重用,作为下一层的输入。 这样不但缓解了梯度消失的现象…

DenseNet解读

Densely Connected Convolutional Networks ,作者清华姚班的刘壮,获得cvpr 2017 best paper。非常值得阅读。 DenseNet优势: (1)解决了深层网络的梯度消失问题 (2)加强了特征的传播 (3&#xff…

MYSQL实现排名函数RANK,DENSE_RANK和ROW_NUMBER

文章目录 1. 排名分类1.1 区别RANK,DENSE_RANK和ROW_NUMBER1.2 分组排名 2. 准备数据3. 不分组排名3.1 连续排名3.2 并列跳跃排名3.3 并列连续排名 4. 分组排名4.1 分组连续排名4.2 分组并列跳跃排名4.3 分组并列连续排名 在MYSQL的最新版本MYSQL8已经支持了排名函数…

tf.keras.layers.Dense函数

函数原型 tf.keras.layers.Dense(units, activationNone, use_biasTrue,kernel_initializerglorot_uniform,bias_initializerzeros, kernel_regularizerNone,bias_regularizerNone,activity_regularizerNone, kernel_constraintNone,bias_constraintNone, **kwargs )函数说明 …

DenseNet模型

《Densely Connected Convolutional Networks》阅读笔记 代码地址:https://github.com/liuzhuang13/DenseNet 首先看一张图: 稠密连接:每层以之前层的输出为输入,对于有L层的传统网络,一共有 L 个连接,对于DenseNe…

深入理解 keras 中 Dense 层参数

目录 引言深入理解 Dense 层的用法查看参数输入尺寸输出尺寸示例:用法完整示例示例一: 最小网络示例二:多维度数据示例三:特殊情况,待讨论 附录 引言 大家或许已经对深度学习不陌生了。不管是养家糊口工作还是科研学习早日毕业&a…

Keras大法(4)——Dense方法详解

Keras大法(4)——Dense方法详解 (一)keras.layers.Dense方法(二)使用示例(三)总 结 (一)keras.layers.Dense方法 在开始定义模型之前,我们有必要…

dense层、激活函数、输出层设计

Tensorflow——tf.layers.dense dense:全连接层 对于层方式的实现的时候! layers.Dense(units,activation)函数一般只需要指定输出节点数Units和激活函数类型即可。输入节点数将根据第一次运算时输入的shape确定,同时输入、输出节点自动创建…

Dense层

1 常见参数 model.add(Dense(units, #输出的大小(神经元个数)activationNone, #激活函数use_biasTrue, #是否添加偏置kernel_initializerglorot_uniform, #权重矩阵初始化bias_initializerzeros, #偏置初始化kernel_regularizerNone, #权重矩阵的正则函…

Keras中dense层原理及用法解释

文章目录 一.全连接层Fully Connection作用二.API解释2.1 示例1:dense层为输入层2.2 示例2:dense层为中间层2.3 示例3:dense层为输出层 三.实现过程四.数学解释 一.全连接层Fully Connection作用 全连接的核心操作就是矩阵向量乘积 y W ∗…

矩阵运算实现求样本与样本之间欧式距离

前言 最近需要写关于kmeans的一些小程序,需要计算距离,直接写for循环又特别慢,再要是样本多一点,那简直了。细细一想,需要计算距离的地方还真不少,kmeans、KNN、图等等。 1. 理论指导 小学学过的公式&am…

实现两个点集的欧式距离和cos距离和索引值寻找(含有两种解法,for循环和矩阵操作)

一.计算欧式距离 1,直接for循环 两个点集points1,points2,用dist来存储距离 points1np.array([[1,2],[3,4]]) points2 np.array([[5, 6],[7,8]]) dist np.zeros(shape[points1.shape[0],points2.shape[0]]) for i in range(points1.sha…

计算样本欧式距离——python

任务描述 本关实现一个函数来计算欧几里得距离。 相关知识 通常数据集中的样本都可描述为一个 n 维向量 。每一个维度代表样本的一个属性。比如,对于用户 x 而言,其属性可能是收入、年龄、工作时间等,对于电影而言,其属性可能…