Monitor
Monitor
首先,monitor是一个同步工具,提供线程(进程)被阻塞和被唤醒的管理机制。
semaphore 和 mutex 在编程上容易出错,因为我们需要去亲自操作变量以及对 进程/线程 进行阻塞和唤醒。
monitor 这个机制之所以被称为**“更高级的原语”,那么它就不可避免地需要对外屏蔽掉这些机制,并且在内部**实现这些机制,使得使用 monitor 的人看到的是一个简洁易用的接口。
监视器Monitor内部,是怎么做到线程同步的(JAVA中Monitor的实现)。
Monitor机制 的实现,属于编译器的工作。
在java虚拟机中,每一个对象头都关联着Monitor,每一个监视器和一个对象引用相关联,为了实现监视器的互斥功能,每个对象都关联着一把锁(有时候又叫“互斥量mutex”,信号量)。
一旦方法或者代码块被Synchronized修饰,那么这个部分就放入了监视器的监事区域,确保一次只能有一个线程执行该部分的代码,线程在获取锁之前不允许执行该部分的代码。
monitor的基本元素
a 临界区—被synchronized修饰的方法、代码块。
b monitor对象以及锁
c 条件变量以及定义在monitor对象上的wait signal操作
首先,monitor机制是为了互斥进入临界区,所以那些被阻塞的线程就需要一个monitor object来协助保存被阻塞的线程(monitor object内部会有相应的数据结构,如列表等);
其次,monitor机制本质上是基于互斥量mutex(值为1的信号量)这种基本原语的,所以 monitor object 还必须维护一个基于 mutex 的锁。
还有,引入一个条件变量决定什么时候阻塞/唤醒线程。程序员对该条件变量的定义有很大的自主性。不过,由于 monitor object 内部采用了数据结构来保存被阻塞的队列,因此它也必须对外提供两个 API 来让线程进入阻塞状态以及之后被唤醒,分别是 wait 和 notify。
- 在java中,临界区,就是被synchronized修饰的方法、代码块。
- monitor object就是synchronized关联的对象。synchronized 如果修饰的是实例方法,那么其关联的对象实际上是 this,如果修饰的是类方法,那么其关联的对象是 this.class。
- monitor 的机制中,monitor object 充当着维护 mutex以及定义 wait/signal API 来管理线程的阻塞和唤醒的角色。
在java中,java.lang.Object类满足上述要求,所以任何一个JAVA对象都可以作为Monitor机制的monitor object。
而在本人的另一篇文章中有说到相关对象在内存中的存储,详细请看:《对象在内存中的存储&&基本类型和包装类&&java类型转换》
了解了对象在内存中的存储之后,我们来看一下,monitor在对象中是如何存储的:
Java 对象存储在内存中,分别分为三个部分,即对象头、实例数据和对齐填充,而在其对象头中,保存了锁标识(其中的重量级锁指的就是synchronized锁)。
详细的对象头的介绍见:《Synchronized详解(可重入、Monitor原理等)》
同时,java.lang.Object 类定义了 wait(),notify(),notifyAll() 方法,这些方法的具体实现,依赖于一个叫 ObjectMonitor 模式的实现,这是 JVM 内部基于 C++ 实现的一套机制,基本原理如下所示:
当一个线程需要获取 Object 的锁时,会被放入 EntrySet 中进行等待,如果该线程获取到了锁,成为当前锁的 owner。如果根据程序逻辑,一个已经获得了锁的线程缺少某些外部条件,而无法继续进行下去(例如生产者发现队列已满或者消费者发现队列为空),那么该线程可以通过调用 wait 方法将锁释放,进入 wait set 中阻塞进行等待,其它线程在这个时候有机会获得锁,去干其它的事情,从而使得之前不成立的外部条件成立,这样先前被阻塞的线程就可以重新进入 EntrySet 去竞争锁。这个**外部条件**在 monitor 机制中称为**条件变量**。