• Java并发编程中的基础概念Monitor


    你好,这里是codetrend专栏“高并发编程基础”。

    引言

    在Java并发编程中,Monitor(监视器)是一种同步机制,用于实现线程间的互斥访问和共享资源的同步。它是一种基本的并发控制原语,在Java中以对象的形式存在。

    每个Java对象都有一个与之关联的Monitor,可以通过synchronized关键字来使用该Monitor。当一个线程获得了一个对象的Monitor锁时,它就可以进入临界区,执行对应的同步代码块或方法。其他线程必须等待该Monitor的锁释放后才能进入临界区。

    Monitor提供了以下核心概念:

    1. 锁定(Locking):一个Monitor只能被一个线程锁定,其他线程需要等待锁的释放才能继续执行。
    2. 互斥性(Mutual Exclusion):在任意时刻,只能有一个线程执行Monitor中的同步代码,并保证线程间的互斥访问。
    3. 条件变量(Condition Variables):Monitor可以使用条件变量来实现线程间的协作与通信。通过wait()方法释放锁并等待条件满足,通过notify()或notifyAll()方法唤醒等待的线程。
    4. 线程调度(Thread Scheduling):Monitor中的线程遵循一定的调度规则,例如公平锁会按照先后顺序唤醒等待的线程。

    Monitor在Java中被广泛应用于同步代码块、同步方法、管程等场景,用于保护共享资源的访问,避免并发访问的竞态条件和数据不一致问题。通过使用Monitor,可以实现线程安全和正确的多线程编程。

    Java对象头

    Java对象头(Object Header)是Java虚拟机中用于管理对象的一部分元数据,存储在每个Java对象的内存中。它包含了一些重要的信息,用于支持对象的运行时特性和垃圾回收。

    Java对象头通常占用一个机器字(Word)大小,具体大小取决于虚拟机的实现和运行环境。

    Java对象头中包含的信息可以分为两类:

    1. Mark Word(标记字段):Mark Word是对象头中最重要的字段,用于存储对象的运行时数据和锁相关的信息。它可以包含以下内容:

      • 对象的哈希码(HashCode):用于快速比较对象是否相等。
      • 锁状态标志:记录对象的锁状态,比如无锁、偏向锁、轻量级锁、重量级锁等。
      • GC标记位:用于标记对象是否被垃圾回收器访问过。
      • 偏向锁的线程ID:当对象被偏向锁锁定时,记录拥有偏向锁的线程ID。
      • 偏向时间戳:记录上次成功获取偏向锁的时间戳。
    2. 类型指针(Klass Pointer):类型指针指向对象的类元数据,用于确定对象的类型信息,包括方法表、字段表等。通过类型指针,虚拟机可以确定对象的具体类型,并进行动态分派。

    Java对象头在内存中紧跟在对象的地址之后,它是实现Java虚拟机的关键组成部分。对象头的布局和具体内容可能因不同的虚拟机实现而有所差异,但它们都具有类似的作用,用于支持对象的运行时特性、锁机制和垃圾回收。

    Monitor对象

    每个 Java 对象都可以关联一个 Monitor 对象,这是通过对象头中的 Mark Word 实现的。在对象头中,Mark Word 可以存储一些运行时数据和锁相关的信息,其中一个重要的信息就是指向 Monitor 对象的指针。当我们使用 synchronized 给对象上锁时,该对象的 Mark Word 就会被设置为指向 Monitor 对象的指针,这样就将对象与 Monitor 对象关联了起来。

    这种关联方式是基于“重量级锁”的实现方式。在 Java 中,synchronized 关键字的实现有两种方式:轻量级锁和重量级锁。轻量级锁是一种基于对象头中的 CAS 操作实现的锁,用于提高并发性能。但是,当轻量级锁无法满足锁的需求时,就会升级为重量级锁。重量级锁是一种基于 Monitor 对象实现的锁,它可以保证多线程之间的互斥访问和同步执行。

    在重量级锁的实现中,每个 Monitor 对象都与一个互斥锁相关联,用于控制对共享资源的访问。当一个线程尝试获得一个被锁定的对象时,它就会阻塞,并且该对象的 Mark Word 中的锁状态标志会被设置为“重量级锁”。此时,该对象就不再使用轻量级锁了,而是使用基于 Monitor 对象实现的重量级锁。同时,该对象头的 Mark Word 中就被设置指向 Monitor 对象的指针,将对象与 Monitor 对象关联起来。

    因此,Java 对象头中的 Mark Word 和 Monitor 对象是实现 Java 中锁机制的两个关键元素。它们共同保证了多线程之间的互斥访问和同步执行,确保了程序的正确性和安全性。在多线程编程中,需要充分理解对象头和 Monitor 对象的作用,合理地使用 synchronized 关键字,才能编写出高效、安全和正确的多线程程序。

    Java对象
    对象头Object Header
    Mark Word
    Monitor对象指针

    编程示例

    示例代码如下:

    package engineer.concurrent.battle.fcontact;
    
    public class MonitorObjectTest {
        static final Object lock = new Object();
        static int counter = 0;
        public static void main(String[] args) {
            synchronized (lock) {
                counter++;
                System.out.println(counter);
            }
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    通过 javap -c MonitorObjectTest 输出字节码如下:

    public class engineer.concurrent.battle.fcontact.MonitorObjectTest {
      static final java.lang.Object lock;
    
      static int counter;
    
      public engineer.concurrent.battle.fcontact.MonitorObjectTest();
        Code:
           0: aload_0
           1: invokespecial #1                  // Method java/lang/Object."":()V
           4: return
    
      public static void main(java.lang.String[]);
        Code:
           0: getstatic     #7                  // Field lock:Ljava/lang/Object;
           3: dup
           4: astore_1
           5: monitorenter                      // 将 lock对象 MarkWord 置为 Monitor 指针
           6: getstatic     #13                 // Field counter:I
           9: iconst_1
          10: iadd
          11: putstatic     #13                 // Field counter:I
          14: getstatic     #17                 // Field java/lang/System.out:Ljava/io/PrintStream;
          17: getstatic     #13                 // Field counter:I
          20: invokevirtual #23                 // Method java/io/PrintStream.println:(I)V
          23: aload_1
          24: monitorexit                        // 将 lock对象 MarkWord 重置, 唤醒 EntryList
          25: goto          33
          28: astore_2
          29: aload_1
          30: monitorexit
          31: aload_2
          32: athrow
          33: return
        Exception table:
           from    to  target type
               6    25    28   any
              28    31    28   any
    
      static {};
        Code:
           0: new           #2                  // class java/lang/Object
           3: dup
           4: invokespecial #1                  // Method java/lang/Object."":()V
           7: putstatic     #7                  // Field lock:Ljava/lang/Object;
          10: iconst_0
          11: putstatic     #13                 // Field counter:I
          14: return
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48

    通过字节码可以发现 锁开始会执行monitorenter,锁结束会执行monitorexit。

    关于作者

    来自一线全栈程序员nine的探索与实践,持续迭代中。

    欢迎关注公众号“雨林寻北”或添加个人卫星codetrend(备注技术)。

  • 相关阅读:
    如何写出有效的单元测试
    docker安装&部署vue
    Elasticsearch是如何通过倒排索引来查询数据的
    域名解析与记录
    如何编写优秀的测试用例,建议收藏和转发
    【无标题】
    json 数据展平pd.json_normalize
    时间轴_量子计算机
    HTML静态网页作业——澳门英文旅游网站设计与实现HTML+CSS+JavaScript
    低/无代码开发系统集成能力有多强?一文告诉你
  • 原文地址:https://blog.csdn.net/sunzroad/article/details/137428607