深入理解多线程(四)—— Moniter的实现原理
在深入理解多线程(一)——Synchronized的实现原理中介绍过关于Synchronize
的实现原理,无论是同步方法还是同步代码块,无论是ACC_SYNCHRONIZED
还是monitorenter
、monitorexit
都是基于Monitor
实现的,那么这篇来介绍下什么是Monitor。
操作系统中的管程
如果你在大学学习过操作系统,你可能还记得管程(monitors)在操作系统中是很重要的概念。同样Monitor在java同步机制中也有使用。
管程 (英语:Monitors,也称为监视器) 是一种程序结构,结构内的多个子程序(对象或模块)形成的多个工作线程互斥访问共享资源。这些共享资源一般是硬件设备或一群变量。管程实现了在一个时间点,最多只有一个线程在执行管程的某个子程序。与那些通过修改数据结构实现互斥访问的并发程序设计相比,管程实现很大程度上简化了程序设计。 管程提供了一种机制,线程可以临时放弃互斥访问,等待某些条件得到满足后,重新获得执行权恢复它的互斥访问。
Java线程同步相关的Moniter
在多线程访问共享资源的时候,经常会带来可见性和原子性的安全问题。为了解决这类线程安全的问题,Java提供了同步机制、互斥锁机制,这个机制保证了在同一时刻只有一个线程能访问共享资源。这个机制的保障来源于监视锁Monitor,每个对象都拥有自己的监视锁Monitor。
先来举个例子,然后我们在上源码。我们可以把监视器理解为包含一个特殊的房间的建筑物,这个特殊房间同一时刻只能有一个客人(线程)。这个房间中包含了一些数据和代码。
如果一个顾客想要进入这个特殊的房间,他首先需要在走廊(Entry Set)排队等待。调度器将基于某个标准(比如 FIFO)来选择排队的客户进入房间。如果,因为某些原因,该客户客户暂时因为其他事情无法脱身(线程被挂起),那么他将被送到另外一间专门用来等待的房间(Wait Set),这个房间的可以可以在稍后再次进入那件特殊的房间。如上面所说,这个建筑屋中一共有三个场所。
总之,监视器是一个用来监视这些线程进入特殊的房间的。他的义务是保证(同一时间)只有一个线程可以访问被保护的数据和代码。
Monitor其实是一种同步工具,也可以说是一种同步机制,它通常被描述为一个对象,主要特点是:
对象的所有方法都被“互斥”的执行。好比一个Monitor只有一个运行“许可”,任一个线程进入任何一个方法都需要获得这个“许可”,离开时把许可归还。
通常提供singal机制:允许正持有“许可”的线程暂时放弃“许可”,等待某个谓词成真(条件变量),而条件成立后,当前进程可以“通知”正在等待这个条件变量的线程,让他可以重新去获得运行许可。
监视器的实现
在Java虚拟机(HotSpot)中,Monitor是基于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类中提供了几个方法:
获得锁
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();
}
}
释放锁
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是默认开启的),这些操作都是为了在线程之间更高效的共享数据 ,解决竞争问题。后面的文章会继续介绍这几种锁以及他们之间的关系。
Java Synchronized实现原理
JVM源码分析之Object.wait/notify实现
Linux Kernel CMPXCHG函数分析
从jvm源码看synchronized
from:https://www.hollischuang.com/archives/2030
深入理解多线程(四)—— Moniter的实现原理相关推荐
- 深入理解ButterKnife源码并掌握原理(四)
到此我们整个的流程算分析完了. 最后我们看下对外提供的API bind 方法 那么还差一步,什么时候都要我们生成的java文件呢?答案是: ButterKnife.bind(this);方法. 我们看 ...
- 深入理解多线程(五)—— Java虚拟机的锁优化技术
本文是<深入理解多线程>的第五篇文章,前面几篇文章中我们从synchronized的实现原理开始,一直介绍到了Monitor的实现原理. 前情提要 通过前面几篇文章,我们已经知道: 1.同 ...
- 深入理解Java并发之synchronized实现原理
[版权申明]未经博主同意,谢绝转载!(请尊重原创,博主保留追究权) http://blog.csdn.net/javazejian/article/details/72828483 出自[zejian ...
- 图解ARP协议(四)代理ARP原理与实践(“善意的欺骗”)
一.代理ARP概述 我:当电脑要访问互联网上的服务器,目标MAC是什么? 很多小伙伴在刚学习网络协议的时候,经常这样直接回应:不就是服务器的MAC嘛! 这时我会反问:那电脑怎么拿到这个服务器的MAC地 ...
- 《深入理解Kafka:核心设计与实践原理》笔误及改进记录
2019年2月下旬笔者的有一本新书--<深入理解Kafka:核心设计与实践原理>上架,延续上一本<RabbitMQ实战指南>的惯例,本篇博文用来记录现在发现的一些笔误,一是给购 ...
- 深入理解ButterKnife源码并掌握原理(三)
上两篇我们分析完了处理器的process方法的findAndParseTargets方法来获取了一个集合,该集合包含了你使用注解的类的TypeElement和这个类中的注解的实例BindingClas ...
- 深入浅出地理解STM32中的定时器工作原理
深入浅出地理解STM32中的定时器工作原理 一.如何实现延时 1 纯硬件电路 2 纯软件编程 3 可编程定时/计数器 二.可编程定时/计数器有哪些功能? 三.STM32F103 的定时器有哪些 1 基 ...
- APM飞控学习之路:2 四旋翼的工作原理与系统组成
"一叶障目,不见泰山".在研究四旋翼飞行器之前,有必要从整体介绍其工作原理.主要部件.技术名词等基础知识.不然就像羊入虎口,陷入一大堆不同层次的资料,难觅出口.接下我就抛砖引玉,尽 ...
- tcp当主动发出syn_一文读懂TCP四次挥手工作原理及面试常见问题汇总
简述 本文主要介绍TCP四次挥手的工作原理,以及在面试中常见的问题. 字段含义 seq序号:Sequence Number,占32位,用来标识从TCP源端向目的端发送的字节流,发起方发送数据时对此进行 ...
- 一文带你理解Java中Lock的实现原理
转载自 一文带你理解Java中Lock的实现原理 当多个线程需要访问某个公共资源的时候,我们知道需要通过加锁来保证资源的访问不会出问题.java提供了两种方式来加锁,一种是关键字:synchron ...
最新文章
- Nativefier 网页转桌面程序 app
- idea maven打jar包_Dev 日志 | 如何将 jar 包发布到 Maven 中央仓库
- yum 快速搭建lnmp环境
- Oracle树查询总结
- POJ 3691 DNA repair AC自动机 + DP
- python爬虫代码房-Python爬虫一步步抓取房产信息
- Virtools 3D行为编程系列(一)
- SQLServer 游标简介与使用说明[转]
- 优化:更优雅的异步代码?
- android 限制edittext 最大输入字符数
- flink sink jdbc没有数据_No.2 为什么Flink无法实时写入MySQL?
- 两台电脑如何实现共享文件
- android 布局图片缩放,Android中进行图片缩放显示
- c#语言求两个数最大公约数,C#趣味程序---求两个数的最大公约数和最小公倍数...
- huobi程序化交易项目
- 磁盘压缩卷只能压缩一半
- CSS错误 Do not use empty rulesets
- 字节跳动2018.11校招测试岗笔试(回忆版)
- WebLogic中间件安装与介绍(详细版约2万字)
- 饕餮盛宴 | 人工智能与未来城市
热门文章
- 灰度图像和彩色图像的直方图均衡化(python实现)
- 1050ti比1050强多少 gtx1050和gtx1050ti差距大吗
- 6月6日重庆 减肥美容、无痕线雕提升技术精品班 (顾春英)
- php独孤九剑,(独孤九剑)--PHP 视频学习 -- 文件系统
- Python多线程操作
- 牛客网浙江大学机试--找出直系亲属
- centos radius mysql_[原创]CentOS下Radius服务器搭建
- 《IDSSIM:基于改进的疾病语义相似度方法的lncRNA功能相似度计算模型》论文梳理
- hazy的面试小笔记之计网(持续更新)
- java.io.IOException:Permission denied