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 关键字,才能编写出高效、安全和正确的多线程程序。

graph LR A(Java对象) --> B[对象头Object Header] B --> C[Mark Word] B --> D[Monitor对象指针]

编程示例

示例代码如下:

java 复制代码
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);
        }
    }
}

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

shell 复制代码
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."<init>":()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."<init>":()V
       7: putstatic     #7                  // Field lock:Ljava/lang/Object;
      10: iconst_0
      11: putstatic     #13                 // Field counter:I
      14: return
}

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

关于作者

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

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

相关推荐
考虑考虑32 分钟前
JDK9中的dropWhile
java·后端·java ee
想躺平的咸鱼干40 分钟前
Volatile解决指令重排和单例模式
java·开发语言·单例模式·线程·并发编程
hqxstudying1 小时前
java依赖注入方法
java·spring·log4j·ioc·依赖
·云扬·1 小时前
【Java源码阅读系列37】深度解读Java BufferedReader 源码
java·开发语言
Bug退退退1232 小时前
RabbitMQ 高级特性之重试机制
java·分布式·spring·rabbitmq
小皮侠2 小时前
nginx的使用
java·运维·服务器·前端·git·nginx·github
Zz_waiting.2 小时前
Javaweb - 10.4 ServletConfig 和 ServletContext
java·开发语言·前端·servlet·servletconfig·servletcontext·域对象
全栈凯哥2 小时前
02.SpringBoot常用Utils工具类详解
java·spring boot·后端
兮动人3 小时前
获取终端外网IP地址
java·网络·网络协议·tcp/ip·获取终端外网ip地址
呆呆的小鳄鱼3 小时前
cin,cin.get()等异同点[面试题系列]
java·算法·面试