Monitor概念

Monitor 被翻译为监视器管程
每个 Java 对象都可以关联一个 Monitor 对象, 如果使用 synchronized 给对象上锁(重量级)之后,该对象头的 Mark Word 中就被设置指向 Monitor 对象的指针
Monitor 对象其实是操作系统自己产生的,而不是Java运行出来的。
例如:Integer和int关键字,他们所占用的字节分别为8个字节+4个字节,8个字节代表的是Monitor带的Java对象头,能够让操作系统找到并调用。

Java 对象头

以 32 位虚拟机为例

普通对象

  1. |----------------------------------------------------------|
  2. | Object Header (64 bits) |
  3. |----------------------------------------------------------|
  4. | Mark Word (32 bits) | Klass Word (32 bits) |
  5. |----------------------------------------------------------|

Mark Word : 用于存储对象自身的运行时数据, 如哈希码 (HashCode) 、GC 分代年龄、锁状态标志、线程持有的锁、偏向线程ID等。同时考虑到虚拟机的空间效率,Mark Word 被设计成一个有着动态定义的数据结构。
Klass Word类型定义信息 , Java 虚拟机通过这个指针来确定该对象是哪个类的实例(并不是所有的虚拟机都必须在对象数据上保留类型指针)。此外,如果对象是一个 Java 数组,那在对象头中还必须有一块用于记录数组长度的数据,因为如果数组的长度是不确定的,将无法推断出数组的大小。

数组对象

  1. |--------------------------------------------------------------------------------------|
  2. | Object Header (96 bits) |
  3. |--------------------------------------------------------------------------------------|
  4. | Mark Word (32 bits) | Klass Word (32 bits) | array length (32bits) |
  5. |--------------------------------------------------------------------------------------|

其中 Mark Word 结构 (动态数据结构)为

存储内容 标志位 状态
对象哈希码、对象分代年龄 01 未锁定 | Normal
指向锁记录的指针 00 轻量级锁定 | LightWeight Locked
指向重量级锁的指针 10 膨胀(重量级锁定)| HeavyWeight Locked
空,不需要记录信息 11 GC标记 | Marked for GC
偏向线程ID、便向时间戳、对象分代年龄 01 可偏向 | Biased

32 位虚拟机

  1. |-------------------------------------------------- |-----------------------------------|
  2. | Mark Word (32 bit) | State |
  3. |-------------------------------------------------- |-----------------------------------|
  4. | hashcode:25 | age:4 | biased_block:0 |01 | Normal |
  5. |-------------------------------------------------- |-----------------------------------|
  6. | thread:23 |epoch:2| age:4 | biased_block:1 |01 | Biased |
  7. |-------------------------------------------------- |-----------------------------------|
  8. | ptr_to_lock_record:30 |00 | LightWeight Locked |
  9. |-------------------------------------------------- |-----------------------------------|
  10. | ptr_to_heavyweight_monitor:30 |10 | HeavyWeight Locked |
  11. |-------------------------------------------------- |-----------------------------------|
  12. | |11 | Marked for GC |
  13. |-------------------------------------------------- |-----------------------------------|

64 位虚拟机

  1. |-------------------------------------------------------------|-------------------------|
  2. | Mark Word (64 bit) | State |
  3. |-------------------------------------------------------------|-------------------------|
  4. | unused:25 | hashcode:31 | unused:1 | age:4 | biased_block:0 | 01 | Normal |
  5. |-------------------------------------------------------------|-------------------------|
  6. | thread:23 | epoch:2 | unused:1 | age:4 | biased_block:1 | 01 | Biased |
  7. |-------------------------------------------------------------|-------------------------|
  8. | ptr_to_lock_record:62 | 00 | LightWeight Locked |
  9. |-------------------------------------------------------------|-------------------------|
  10. | ptr_to_heavyweight_monitor:62 | 10 | HeavyWeight Locked |
  11. |-------------------------------------------------------------|-------------------------|
  12. | | 11 | Marked for GC |
  13. |-------------------------------------------------------------|-------------------------|

一个Monitor对象的使用情况:
Monitor概念 - 图1
线程切换
Monitor概念 - 图2

  • 刚开始 Monitor 中 Owner 为 null
  • 当 Thread-0 执行 synchronized(object) 上锁就会将 Monitor 的所有者 Owner 置为 Thread-0, 同时对象 object 中的 Mark Word 的 state 也会发生相应改变。
  • 在 Thread-0 上锁的过程中,如果 Thread-1, Thread-2, Thread-3 也来执行 synchronized(object), 就会进入 EntryList 变成 BLOCKED 状态
  • Thread-0 执行完同步代码块的内容,然后唤醒 EntryList 中等待的线程来竞争锁,竞争时是非公平的
  • 图中 WaitSet 中的 Thread-4, Thread-5 是之前获得过锁,但条件不满足进入 WAITING 状态的线程,可以由 notifyAll() , notify() 唤醒

注意:

  • synchronized 必须是进入同一个对象的 monitor 才有上述的效果
  • 不加 synchronized 的对象不会关联监视器,不遵从以上规则

    C++源码分析

    hpp:头文件的申明 ```cpp // 底层使用的是双向链表 class ObjectWaiter : public StackObj {
    1. ObjectWaiter * volatile _next; // (指针)指向下一个线程(唤醒策略,双向链表,好处是当一个线程优先级较高,从waitSet中移除了,然后指针只要一修改,该线程就被移除了,然后剩下的链表就可以重新连上了)
    ObjectWaiter * volatile _prev; // (指针)指向上一个线程 }

// 锁的状态 class ObjectMonitor { public: enum { OM_OK, // no error OM_SYSTEM_ERROR, // operating system error OM_ILLEGAL_MONITOR_STATE, // IllegalMonitorStateException OM_INTERRUPTED, // Thread.interrupt() OM_TIMED_OUT // Object.wait() timed out };

//构造器 ObjectMonitor() { _header = NULL; _count = 0; _waiters = 0; _recursions = 0; // 递归;线程的重入次数,典型的System.out.println,嵌套的数量,线程进入一次加一的数量 _object = NULL; // 对应synchronized (object)对应里面的object _owner = NULL; // 标识拥有该monitor的线程 _WaitSet = NULL; // 因为调用object.wait()方法而被阻塞的线程会被放在该队列中 _WaitSetLock = 0 ; // 保护等待队列 自旋锁 _Responsible = NULL; _succ = NULL; _cxq = NULL; // 竞争队列,所有请求锁的线程首先会被放在这个队列中 FreeNext = NULL; _EntryList = NULL; // 阻塞;第二轮竞争锁仍然没有抢到的线程(偏向/可重入) _SpinFreq = 0; _SpinClock = 0; OwnerIsThread = 0; } protected: // protected for jvmtiRawMonitor void volatile _owner; // pointer to owning thread OR BasicLock volatile jlong _previous_owner_tid; // thread id of the previous owner of the monitor volatile intptr_t _recursions; // recursion count, 0 for first entry private: int OwnerIsThread ; // _owner is (Thread ) vs SP/BasicLock ObjectWaiter volatile _cxq ; // LL of recently-arrived threads blocked on entry. // The list is actually composed of WaitNodes, acting // as proxies for Threads. protected: ObjectWaiter volatile _EntryList ; // Threads blocked on entry or reentry. protected: ObjectWaiter * volatile _WaitSet; // LL of threads wait()ing on the monitor private: volatile int _WaitSetLock; // protects Wait Queue - simple spinlock } ```