一、Java对象头

        由于Java面向对象的思想,在JVM中需要大量存储对象,存储时为了实现一些额外的功能,需要在对象中添加一些标记字段用于增强对象功能,这些标记字段组成了对象头。

        对象头包含两部分:运行时元数据Mark Word)和类型指针Klass Word

以32位虚拟机为例:

普通对象:

数组对象:数组对象还需要记录数组长度

其中,运行时元数据Mark Word结构为

  1. 哈希值(HashCode),可以看作是堆中对象的地址
  2. GC分代年龄(年龄计数器) (用于新生代from/to区晋升老年代的标准, 阈值为15)
  3. 锁状态标志 (用于JDK1.6对synchronized的优化 -> 轻量级锁)
  4. 线程持有的锁
  5. 偏向线程ID (用于JDK1.6对synchronized的优化 -> 偏向锁)
  6. 偏向时间戳

类型指针: 指向类元数据InstanceKlass,确定该对象所属的类型。指向的其实是方法区中存放的类元信息

 二、Monitor(锁)

         Monitor被翻译为监视器管程

         每个 Java 对象都可以关联一个 Monitor 对象,如果使用 synchronized 给对象上锁(重量级)之后,对象头的Mark Word 中就被设置指向 Monitor 对象的指针(将对象与Monitor对象关联)

 图解: 

        如下图所示,我们有一个临界区代码,当Thread2执行到synchronized(obj),访问共享资源的时候:

  1. 首先会将synchronized中的锁对象中对象头的MarkWord去尝试指向操作系统提供的Monitor对象,让锁对象中的MarkWord和Monitor对象相关联. 如果关联成功, 将obj对象头中的MarkWord对象状态从01改为10。
  2. 因为该Monitor没有和其他的obj的MarkWord相关联,所以Thread2就成为了该Monitor的Owner(所有者)。
  3. 然后,又来了一个Thread1执行synchronized(obj)代码,它首先会检查是否能执行临界区代码,即检查obj是否关联了Montior,此时已经有关联了, 它就会去看看该Montior有没有所有者(Owner), 发现有所有者了(Thread2);Thread1也会和该Monitor关联, 该线程就会进入到它的EntryList(阻塞队列),EntryList是一个列表,若此时Thread3也执行到synchronized(obj)代码,也会进入阻塞队列。
  4. Thread2执行完临界区代码后, Monitor的Owner(所有者)就空出来了. 此时就会通知Monitor中的EntryList阻塞队列中的线程, 这些线程通过竞争, 成为新的所有者。

  • 刚开始 Monitor 中 Owner 为 null
  • 当 Thread-2 执行 synchronized(obj) 就会将 Monitor 的所有者 Owner 置为 Thread-2Monitor中只能有一个 Owner
  • 在 Thread-2 上锁的过程中,如果 Thread-3,Thread-4,Thread-5 也来执行 synchronized(obj),就会进入 EntryList BLOCKED
  • Thread-2 执行完同步代码块的内容,然后唤醒 EntryList 中等待的线程来竞争锁,竞争时是非公平的
  • 图中 WaitSet 中的 Thread-0,Thread-1 是之前获得过锁,但条件不满足进入 WAITING 状态的线程

注意:

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

2.1 synchronized原理

示例程序:

static final Object lock = new Object();
static int counter = 0;
 
public static void main(String[] args) {
    synchronized (lock) {
        counter++;
    }
}

对应字节码:

    Code:
      stack=2, locals=3, args_size=1
         0: getstatic     #2                  // <- lock引用 (synchronized开始)
         3: dup
         4: astore_1                          // lock引用 -> slot 1
         5: monitorenter                  // 将 lock对象 MarkWord 置为 Monitor 指针
         6: getstatic     #3                  // <- i
         9: iconst_1                           // 准备常数 1
        10: iadd                                 // +1
        11: putstatic     #3                  // -> i
        14: aload_1                           // <- lock引用
        15: monitorexit                    // 将 lock对象 MarkWord 重置, 唤醒 EntryList

三、结束

        本文主要介绍了Java对象头以及Monitor,借此对synchronized的底层实现原理进行了说明。

Logo

华为开发者空间,是为全球开发者打造的专属开发空间,汇聚了华为优质开发资源及工具,致力于让每一位开发者拥有一台云主机,基于华为根生态开发、创新。

更多推荐