首页 > 其他 > 详细

synchronized底层实现monitor详解

时间:2019-12-31 11:37:09      阅读:879      评论:0      收藏:0      [点我收藏+]

二、ObjectMonitor 结构

前面讲到 java.lang.Object 类定义了 wait(),notify(),notifyAll() 方法。 这些都是 native方法,底层是C++来实现的。 这些方法的具体实现,依赖一个叫做ObjectMonitor模式实现,这是JVM内部C++实现的机制。

技术分享图片

这里有几个比较重要的字段

2.1 _owner 指向持有ObjectMonitor对象的线程地址。

2.2 _WaitSet 存放调用wait方法,而进入等待状态的线程的队列。

2.3 _EntryList 这里是等待锁block状态的线程的队列。

2.4 _recursions 锁的重入次数。

2.5 _count 线程获取锁的次数。

技术分享图片

三、 Monitor 上锁 释放锁

3.1 上锁过程

3.1.1 线程获取资源对象的锁,判断 _owner是否为空。这里操作是通过 CAS操作:比较和交换(Conmpare And Swap),比较新值和旧值的不同,替换,这里会发生ABA问题,接下来文章会详细说明。

3.1.2 如果 _owner为null ,直接把其赋值,指向自己, _owner = self ,同时把重入次数 _recursions = 1, 获取锁成功。

3.1.3 如果 _self == cur 和当前线程一致,说明是重入了, _recursions++ 即可

3.1.4 线程进入对象资源,处理。 同时等待当前线程的释放信号,期间一致持有对象资源的锁。

3.2 释放锁

3.2.1 通过 ObjectMonitor::exit 退出

3.2.2 把线程插入到_EntryList中 _recursions--

3.2.3 再次从 _EntryList 中取出线程

3.2.4 调用unpark退出

 

在我们分析synchronized关键字底层信息时,其中谈到了Monitor对象,它是由C++来实现的,那,到底它长啥样呢?我们在编写同步代码时完全木有看到该对象的存在,所以这次打算真正来瞅一下它的真正面目,而对于这个Hospot代码JDK是并没有开源的,但是社区版本的JDK是开源了,在openjdk上可以阅读得到,所以下面先到openjdk上瞅一下:

技术分享图片

所以点击一下它:

技术分享图片

点击一下:

技术分享图片

 

技术分享图片

然后点击左侧的browser方便我们浏览代码:

技术分享图片

然后定位到这个路径:

技术分享图片

然后点击runtime/,

技术分享图片

 

其中,我们要想看到Monitor对象的源代码就在其中,如下:

技术分享图片

 

 其中.hpp是c++的头文件,其具体的实现是以cpp中,接下来就得打开它们来看我们想了解的东东了,是不是很刺激?对于上一次理论中提到了两个东东:

技术分享图片

那咱们从源码中来看一下具体都表现为啥,首先先来看一下ObjectMonitor.hpp:

技术分享图片

接下来就可以看到ObjectMonitor的类声明了:

技术分享图片

 

 其实对就对应于:

技术分享图片

 

 接下来挑里面的重点内容瞅一下:

技术分享图片

接下来看一下它的成员变量:

技术分享图片

那看一下_WaitSet的定义:

技术分享图片

再看一下_EntryList的定义:

技术分享图片

另外在上一个理论中提到了:

技术分享图片

 

其实也在底层有定义,如下:

技术分享图片

看一下它的定义:

技术分享图片

另外上理论中还提到了它:

技术分享图片

其实也有对应:

技术分享图片

继续来看其它的一个成员变量:

技术分享图片

 

技术分享图片

技术分享图片

 

技术分享图片

 

对于wait()和notify()的底层细节到底是啥样的呢?下面还是先来到openjdk中来打开ObjectMonitor.hpp,其中它里面有一个很重要的类:

技术分享图片

然后我们要分析的wait()和notify()是在它的cpp实现文件中,所以打开ObjectMonitor.cpp来直奔主题:

技术分享图片

技术分享图片

其中我们在Java层看到的wait()方法的最终实现就是这个cpp中的这个方法:

技术分享图片

先纵览一下该方法的实现,代码量比较多,有将近200多行的代码,全部看通是不可能的,挑重点的来看:

技术分享图片

 

而Self其实是一个线程:

技术分享图片

 

所以这句话的是将线程包装成了ObjectWaiter对象了:

技术分享图片

另外会将状态改为等待状态:

技术分享图片

 

另外,在上次的理论中我们知道在调用了wait()之后,会将其加入到WaitSet当中,如:

技术分享图片

 

那么。。具体对应的底层代码在哪呢?看下面:

技术分享图片

好,下面来瞅一下它的具体实现:

技术分享图片

再回到wait()方法继续,其中可以看到有自旋锁的东东:

技术分享图片

技术分享图片

 

下面简单的纵览一下它:

技术分享图片

好,下面再来观测一下notify()方法的细节:

技术分享图片

技术分享图片

 

技术分享图片

 

 咱们看一下DequeueWaiter()方法是怎么实现的:

技术分享图片

 

回到主流程:

技术分享图片

其实在notify()的官网中也有类似的说明:

技术分享图片

 

而文档中提到的“具体实现”其实就是如我们看到cpp中这块的代码,不同的策略其实现也不一样。

照我们之前的理论来说,唤醒的线程会被放到EntryList当中:

技术分享图片

 

 在notify()代码中也能看到:

技术分享图片

然后最后会看到有一个这个代码:

技术分享图片

以上就是从底层c++的角度来审视我们在java上调用的wait()和notify()的具体细节,虽说大致看了一下主流程,实际工作也没啥大的作用,但是!!!对我们的更深入的理解起到了非常重要的作用,毕境可以让你能更加自信的用Java的这些API。

 

 

Monitor

可以把它理解为一个同步工具,也可以描述为一种同步机制,它通常被描述为一个对象。
和万物皆对象一样,所有的Java对象是天生的Monitor,每一个Java对象都有成为Monitor的潜质,因为在Java的设计中 ,每一个Java对象自打娘胎里出来就带了一把看不见的锁,它叫做内部锁或者Monitor锁
Monitor 是线程私有的数据结构,每一个线程都有一个可用monitor record列表,同时还有一个全局的可用列表。
每一个被锁住的对象都会和一个monitor关联(对象头的MarkWord中的LockWord指向monitor的起始地址),同时monitor中有一个Owner字段存放拥有该锁的线程的唯一标识,表示该锁被这个线程占用。其结构如下

 

技术分享图片

Owner:初始时为NULL表示当前没有任何线程拥有该monitor record,当线程成功拥有该锁后保存线程唯一标识,当锁被释放时又设置为NULL
EntryQ:关联一个系统互斥锁(semaphore),阻塞所有试图锁住monitor record失败的线程
RcThis:表示blocked或waiting在该monitor record上的所有线程的个数
Nest:用来实现重入锁的计数
HashCode:保存从对象头拷贝过来的HashCode值(可能还包含GC age)
Candidate:用来避免不必要的阻塞或等待线程唤醒,因为每一次只有一个线程能够成功拥有锁,如果每次前一个释放锁的线程唤醒所有正在阻塞或等待的线程,会引起不必要的上下文切换(从阻塞到就绪然后因为竞争锁失败又被阻塞)从而导致性能严重下降。Candidate只有两种可能的值0表示没有需要唤醒的线程1表示要唤醒一个继任线程来竞争锁。

 

转载:https://www.cnblogs.com/webor2006/p/11443392.html

synchronized底层实现monitor详解

原文:https://www.cnblogs.com/minikobe/p/12123065.html

(0)
(0)
   
举报
评论 一句话评论(0
关于我们 - 联系我们 - 留言反馈 - 联系我们:wmxa8@hotmail.com
© 2014 bubuko.com 版权所有
打开技术之扣,分享程序人生!