Monitor 最底层实现原理(.Net)
经常听大家聊锁的事情,可是为什么就能锁住呢,这个问题可能10个人里面9个人不知道,今天来说明一下具体的原因。
一.原理
1、原理:当调用Monitor.Enter()时,CLR通过在对象头中存储当前线程ID或通过在同步块表中创建同步块并将其索引存储在对象头中来注册锁。
2、下图表明了对象头、同步块表和同步块对象三者之间的关系。
- 同步块表存储了同步块对象的引用。
- 同步块表保存了实体对象的引用(弱引用)。
- 对象头存储了同步块对象在同步块表中的索引。
3、那什么是对象头呢,现在说明一下:
- 对象头:狭义又称同步块索引;
- 对象头是位于对象的上方的;
- 对象头的作用:线程同步、hash值存储、用于GC回收时的标记阶段、用于GC析构阶段;
- 在内存中,头将位于与对象指针的负偏移量:在x86(32位)进程中,偏移量为-4字节,在x64进程中偏移量为-8字节。如下图所示:
二.为什么值类型不能做锁
1:先看一下内存布局:现在让我们看看32位(x86)CLR上引用和值类型实例的内存布局:
2:答案:
三.验证
前面说的都是比较理论的,那么这个理论说的对吗,我们要有一种怀疑的精神来学习,所以要验证一下:
1:验证头对象的位置。
2:验证锁的实现。
三.一验证头对象的位置
通过hashcode来验证。代码如下,非常简单:
1:定义一个类:Consumer。
2:先创建一个Consumer的实例,然后获取次实例的哈希码,然后打印出来。
3:启动程序,先创建一个Consumer的实例,查看此时内存地址的相关信息,此时0x029B605C前面的四个字节都是00000000,如下图所示:
4:获取哈希码,记录头对象信息,此时变成了:02bf8098
5:继续执行程序输出哈希码:46104728
6:拿到对象头信息(02bf8098),转成10进制,看内容是否与打印出来的哈希码一致?
结论:可以看到对象头的信息和哈希码的值是一样,因此可以证明对象头在对象地址的前四位。
三.二验证锁
1、代码如下,非常简单:
- 定义一个类:People ;
- 创建一个锁对象:lockObj ;
- 用Monitor.Enter()加锁;
2、执行代码,创建所对象lockObj,查看该对象的同步块索引:00000000
3:用Monitor.Enter()加锁,查看该对象的同步块索引:00000001。
4:用Monitor. Exit()加锁,查看该对象的同步块索引:00000000
结论:由此可见,锁的功能是通过同步块索引控制的。
四.结论
1:锁是通过对象的同步块索引来进行控制的。
2:CLR通过在对象头中存储当前线程ID或通过在同步块表中创建同步块并将其索引存储在对象头中来注册锁。其他试图获取对象锁的线程必须等到监视器退出持有锁的线程调用。调用Exit时,它会检查是否还有更多的线程等待获取对象的锁,如果没有线程在等待它,Exit将从对象头中清除锁信息。
五.参考文献
https://www.cnblogs.com/eastpig/p/11699747.html
https://www.markopapic.com/csharp-under-the-hood-locking