JVM内部如何进行Java对象锁定和监视器创建

9
假设我有以下代码片段,其中两个线程访问同一个方法,每个同步语句都使用不同的锁对象。代码如下:
public class MyWorker {
private Random random = new Random();

private Object lock1 = new Object();
private Object lock2 = new Object();

private List<Integer> list1 = new ArrayList<>();
private List<Integer> list2 = new ArrayList<>();

private void stageOne() {

    synchronized (lock1) {
        try {
            Thread.sleep(1);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        list1.add(random.nextInt(100));
    }

}

private void stageTwo() {

    synchronized (lock2) {
        try {
            Thread.sleep(1);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        list2.add(random.nextInt(100));
    }

}

private void process() {
    for (int i=0; i<1000; i++) {
        stageOne();
        stageTwo();
    }

}

void main() {

    Thread t1 = new Thread(this::process);

    Thread t2 = new Thread(this::process);

    t1.start();
    t2.start();

    try {
        t1.join();
        t2.join();
    } catch (InterruptedException e) {
        e.printStackTrace();
    }

}

}
我的问题不是关于这段代码的错误或在java方面如何执行。这段代码工作正常。我只是将其作为参考代码,以便回答者有一个特定的场景可以参考。 我想知道JVM如何在内部创建与此实例关联的监视器对象,并根据该场景使用OpenJDK实现进行对象锁定的内部情况。 我期望得到低级别的解释。

我研究了这个主题几天,但找不到深入的解释。这是我经历过的一些发现:

  • 这个stackoverflow问题 Java lock concept how internally works?。但我没有在答案中找到彻底的解释。
  • JLS 17.1提供了一个有关监视器如何在高层次上工作的语言解释,但没有提到"内部发生了什么"。

其中最基本的方法是同步,使用监视器实现。Java中的每个对象都与一个监视器相关联,线程可以锁定或解锁该监视器。在任何时刻只有一个线程可以持有监视器上的锁。任何试图锁定该监视器的其他线程都会被阻塞,直到它们可以获得该监视器的锁。线程t可以多次锁定特定的监视器;每个解锁操作都会撤销一个锁定操作的影响。

  • 参见Bill Venners的《Java虚拟机内部》第20章“线程同步”(英文)
在Java虚拟机中,每个对象和类都与一个监视器逻辑关联。对于对象,关联的监视器保护对象的实例变量。对于类,监视器保护类的类变量。如果一个对象没有实例变量,或者一个类没有类变量,关联的监视器不会保护任何数据。
为了实现监视器的互斥能力,Java虚拟机将锁(有时称为互斥量)与每个对象和类关联起来。锁就像一种特权,只有一个线程可以在任何时候“拥有”它。线程不需要获取锁来访问实例或类变量。然而,如果一个线程确实获得了锁,那么其他线程就无法在同一数据上获得锁,直到拥有锁的线程释放它。(“锁定对象”是指获取与该对象关联的监视器。) 我知道在指令集级别上如何使用 monitorentermonitorexit 操作码来管理同步语句。但是我正在尝试通过JVM源代码层面来深入理解。但由于源代码中有很多内容,因此我仍然难以将OpenJDK源代码与我通过上述链接找到的高层次说明相映射。
因此,是否有熟悉OpenJDK源代码的任何人可以使用OpenJDK源代码为以下与上述代码片段相关的问题提供解释? 我认为 ObjectMonitorBasicLockSynchronizer 类与此解释更相关。
1. 为哪个对象实例创建监视器对象?是为MyWorker对象实例、Object lock1还是两者都有?因为JSL和Bill Vennam的说明都描绘了每个对象与监视器相关联。 2. 如果是为MyWorker对象实例创建的,那么如何为MyWorker对象实例创建监视器? 3. 如何为引用对象Object lock1创建锁定对象? 4. 线程如何通过锁定对象实际锁定监视器?

我不确定,但我认为锁本身是通过测试和设置实现的:https://en.wikipedia.org/wiki/Test-and-set。这些是流行的机器指令,我相信Intel会实现其中之一。如果测试和设置失败,那么我认为会检查所有者,如果是当前线程,则监视器锁仍然成功。否则,线程将被阻止(或在某些情况下会旋转等待)。 - markspace
为什么这个问题被标记为C++? - SergeyA
@SergeyA 因为我希望通过C++编写的OpenJDK虚拟机源代码来得到解释。 - Insightcoder
1
我认为问题不够清晰。您可能需要明确要求指向JDK中的C++代码并解释该代码。 - SergeyA
你没有理解重点。如果你仔细看的话,我已经明确指出这是关于OpenJDK源代码与我的参考Java代码相关的低级解释,因此我引用了来自OpenJDK的源代码文件,我认为这里应该有C++标签。 - Insightcoder
显示剩余4条评论
1个回答

4
监视器对象是为哪个对象实例创建的?
每个Java对象都是监视器对象,包括反射对象,因此您的代码至少具有以下内容:
- `MyWorker` 的类对象 - `Random` 的类对象 - `Object` 的类对象 - `List` 的类对象 - `Integer` 的类对象 - `ArrayList` 的类对象 - ...以及许多其他对象... - 分配给字段 `random` 的 `Random` 实例 - 分配给字段 `lock1` 的 `Object` 实例 - 分配给字段 `lock2` 的 `Object` 实例 - 分配给字段 `list1` 的 `ArrayList` 实例 - 分配给字段 `list2` 的 `ArrayList` 实例 - 分配给局部变量 `t1` 的 `Thread` 实例 - 分配给局部变量 `t2` 的 `Thread` 实例 - 在调用 `add(random.nextInt(100))` 时通过自动装箱创建的每个 `Integer` 实例
监视器对象是为 `MyWorker` 对象实例和 `Object lock1` 引用对象实例都创建的。
如果它是为 `MyWorker` 对象实例创建的,那么监视器对象是由JVM内部创建的。如果它是为 `Object lock1` 引用对象实例创建的,则锁对象是通过调用 `synchronized(lock1)` 方法来创建的。锁对象如何锁定线程取决于JVM内部实现。对此进行“详细说明”超出了本站的范围。

你能给我解释一下HotSpot JVM的实现吗? - Insightcoder
1
@Andreas,你直接的沟通方式很吸引我。 - SergeyA
@Insightcoder 具体是什么解释?很抱歉,我不知道你要的“如何”。---监视器在CPU级别下如何工作?这是一个通用的低级问题,与Java毫无关系。 - Andreas
2
是的,这确实是一个与Java完全无关的通用低级问题。这就是为什么我在问题中提到“我的问题不是关于此代码中的错误或如何执行此代码。”我只是拿Java代码作为参考,以便可以解释低级别的执行方式。我特别寻求有OpenJDK源代码经验的人的帮助。但似乎所有投票支持“否”答案的人甚至都不理解这个问题是关于特定Java代码的低级别解释的。 - Insightcoder
4
@Insightcoder,因为您一次问了很多问题,所以您不应该责怪回答者只回答了其中90%的问题,但是错过了您实际的问题。如果您实际的问题是JVM如何在最低层实现锁定,则您问题文本中的90%已经过时且是无用的噪音。 - Holger
显示剩余12条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接