主线程超过设定的睡眠时间

glo*_*one 1 java multithreading jvm

public static AtomicInteger num = new AtomicInteger(0);

public static void main(String[] args) throws Throwable {
    Runnable runnable = () -> {
        for (int i = 0; i < 1000000000; i++) {
            num.getAndAdd(1);
        }
    };

    Thread t1 = new Thread(runnable);
    Thread t2 = new Thread(runnable);
    t1.start();
    t2.start();

    System.out.println("before sleep");
    Thread.sleep(1000);
    System.out.println("after sleep");

    System.out.println(num);
}
Run Code Online (Sandbox Code Playgroud)

我想设置主线程休眠1000ms,但实际上输出会等到两个子线程计算结束才输出,但是当我把时间调整为100ms时,主线程不会等到结束的子线程。

apa*_*gin 7

这是与 HotSpot Safepoint 机制相关的重要效果。

背景

通常,HotSpot JVM 在循环内添加安全点轮询,以便在 JVM 需要执行 stop-the-world 操作时暂停线程。安全点轮询不是免费的(即它有一些性能开销),因此 JIT 编译器会尝试在可能的情况下消除它。其中一种优化是从计数循环中删除安全点轮询。

for (int i = 0; i < 1000000000; i++)是一个典型的计数循环:它具有单调整数循环变量(计数器)和有限的迭代次数。JDK 8 JIT 在没有安全点轮询的情况下编译此类循环。然而,这是一个非常长的循环;需要几秒钟才能完成。当这个循环运行时,JVM 将无法停止线程。

HotSpot JVM 不仅将安全点用于 GC,还用于许多其他操作。特别是,当有清理任务需要执行时,它会定期停止 Java 线程。周期由-XX:GuaranteedSafepointInterval选项控制,默认为 1000 ms。

你的例子中发生了什么

  1. 您启动两个长的不可中断循环(内部没有安全点检查)。
  2. 主线程进入睡眠状态 1 秒。
  3. 1000 毫秒 (GuaranteedSafepointInterval) 后,JVM 尝试在安全点停止 Java 线程以进行定期清理,但直到计数的循环完成后才能执行此操作。
  4. Thread.sleep方法从本机返回,发现安全点操作正在进行中,并挂起直到操作结束。

此时,主线程正在等待循环完成 - 正如您所观察到的那样。

当您将睡眠持续时间更改为 100 毫秒时,保证的安全点会在Thread.sleep返回后发生,因此该方法不会被阻塞。

或者,如果您保留 1000 毫秒睡眠时间,但增加-XX:GuaranteedSafepointInterval=2000,主线程也不必等待。

修复

-XX:+UseCountedLoopSafepoints选项关闭消除安全点轮询的优化。在这种情况下,Thread.sleep将按预期休眠 1 秒。

另外,如果更改int ilong i,则循环将不再被视为计数,因此您不会看到提到的安全点效果。

从 JDK 10 开始,HotSpot 实现了 Loop Strip Mining 优化,无需太多开销即可解决计数循环中的安全点轮询问题。因此,您的示例应该可以在 JDK 10 及更高版本中正常运行。

问题的详细解释和解决方案可以在这个问题的描述中找到。