【问题标题】:Java concurrency with respect to syncronization block in loop关于循环中同步块的 Java 并发
【发布时间】:2021-08-29 23:43:14
【问题描述】:

我有一个简单的代码,它将同一个对象传递给另一个用于锁定的类(不理想,但假设这是一种可能性)。输出应该是什么?

  1. MorningThread 只是在 for 循环内的同步块中打印“早安”。
  2. EveningThread 只是在 for 循环内的同步块中打印“晚安”。
    private Object lock;

    MorningThread(Object lock) {
        this.lock= lock;
    }

    public void run() {
        for (int i = 0; i < 2; i++) {
            synchronized(lock) {
                System.out.println("Good Morning");
            }
        }
    }
}

class EveningThread extends Thread {
    private Object lock;

    EveningThread(Object lock) {
        this.lock= lock;
    }

    public void run() {
        for (int i = 0; i < 2; i++) {
            synchronized(lock) {
                System.out.println("Good Evening");
            }
        }
    }
}

public class HelloWorld {
    public static void main(String[] args) throws Exception {

        Object lock = new Object();

        new MorningThread(lock ).start();
        new EveningThread(lock ).start();
    }
}

根据我的理解,它可以是各种组合(实际上),但从理论上讲它应该在下面,因为早上会先执行,到这个时候晚上会等待,晚上会在早上等待的时候执行,以后再执行。

  1. 早安
  2. 晚上好
  3. 早安
  4. 晚上好

但奇怪的是它总是(当我在 Windows 机器上运行时)

  1. 早安
  2. 早安
  3. 晚上好
  4. 晚上好

所以我开始认为 JVM 可能会进行一些优化,它可以识别持续流入并且不会锁定 Evening 直到 Morning 结束。那可能是真的吗? (我找不到正确的 Java 文档来调用它)

【问题讨论】:

  • 两个输出都是完全有效和合理的。无法准确预测线程将如何安排执行,因为许多系统条件会影响它们。 Windows 是否正在下载更新?扫描驱动器?写出磁盘缓存?所有这些都占用一个 CPU 内核,因此会影响线程计划运行的方式和时间。您的 MorningThread 完全有可能在 EveningThread 启动之前执行并完成。
  • 内循环是如此之快,以至于无论哪个线程先启动,都可能在第二个线程甚至试图获得锁之前完成。
  • 可能无法观察到某些排列。 JIT 可以展开一个循环,然后进行锁粗化。

标签: java multithreading concurrency thread-safety


【解决方案1】:

据我了解,它可以是[输出]的各种组合(实际上)......

你这句话是对的。

...但理论上应该在下面,因为 Morning 将首先执行,此时 Evening 将等待,而 Evening 执行,Morning 正在等待,以后再执行。

这个说法是错误的。理论上或其他方面不应该期望线程排序。启动一个线程是昂贵的(相对而言),所以在“早安”线程启动后,它会在“晚安”线程启动之前运行、锁定、打印、锁定、打印和完成。

Java 线程被设计为异步运行,在大多数情况下,它们的操作顺序很难或无法预测。以这种方式跑步是他们获得速度的方式。如果您将循环计数器增加到(假设)20000,那么您应该会看到一些混合输出,但它很可能是早上的消息块,然后是晚上的消息块,而不是 M E M E ME ... 任何期待来自线程程序的特定有序输出可能不应该使用线程,或者应该收集输出然后在最后对其进行排序。

几个其他的cmets:

  • 锁对象应该是private final Object lock ...
  • 将锁对象注入线程对象的构造函数是一种很好的模式,而不是“不理想”。
  • System.out.println(...) 也已同步。对System.out 的任何使用都会改变线程程序的运行方式,因此应谨慎使用。

【讨论】:

    【解决方案2】:

    JVM,甚至它的即时 (JIT) 编译器,都无法以有效的方式进行与不同线程中的流相关的优化。

    Java 线程通常与本机操作系统 (OS) 线程密切相关。操作系统启动线程。这意味着一个线程可以在另一个线程开始之前完成!操作系统也会暂停和恢复线程。这几乎可以在任何 CPU 指令中先发​​制人地发生。由于 JVM 或您的 JIT 编译代码都只是程序,用 CPU 指令编码,它们通常甚至不会意识到它们被暂停或恢复。

    因此,JIT 编译器无法构建关于不同线程中流的有意义统计,更不用说优化它们了。

    【讨论】:

      猜你喜欢
      • 2013-08-07
      • 2022-12-10
      • 2011-11-12
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多