主线程超过设定的休眠时间
The main thread exceeds the set sleep time
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);
}
我想设置主线程休眠1000ms,但实际上输出会等到两个子线程计算结束后才输出,但是当我把时间调到100ms时,主线程会不等待子线程结束。
I want to set the main thread to sleep for 1000ms
你做到了。
but in fact the output will wait until the calculation of the two sub-threads ends before outputting
你有一台电脑;这并不神奇;它的资源是有限的。您的 2 个线程是 'busy-spinning' - 时不时地工作而不是睡觉。当然,您的线程将休眠 1000 毫秒。它可能不允许 运行 因为系统正忙于做其他事情(例如你的 2 个计算线程正在忙着旋转)。
如果您想等待一个线程完成,请调用 t1.join()
,它将休眠直到该线程完成。然后调用 t2.join()
,瞧。
如果您想让线程暂时不做任何事情,请使用一种机制让它休眠,例如 Thread.sleep
、wait/notifyAll
或 java.util.concurrent
包。比如轮询一个blockingqueue。
这是与 HotSpot 相关的重要效果 Safepoint mechanism。
背景
通常 HotSpot JVM 在循环内添加一个安全点轮询,以允许在 JVM 需要执行 stop-the-world 操作时暂停线程。安全点轮询不是免费的(即它有一些性能开销),因此 JIT 编译器会尽可能地尝试消除它。其中一项优化是从 counted loops.
中删除安全点轮询
for (int i = 0; i < 1000000000; i++)
是一个典型的计数循环:它有一个单调整数循环变量(计数器)和有限的迭代次数。 JDK 8 JIT 在没有安全点轮询的情况下编译这样的循环。但是,这是一个很长的循环;需要几秒钟才能完成。虽然此循环是 运行,但 JVM 将无法停止线程。
HotSpot JVM 不仅将安全点用于 GC,还用于 . In particular, it stops Java threads periodically when there are 。周期由 -XX:GuaranteedSafepointInterval
选项控制,默认为 1000 毫秒。
你的例子发生了什么
- 你开始了两个不可中断的长循环(内部没有安全点检查)。
- 主线程休眠 1 秒。
- 1000 毫秒 (GuaranteedSafepointInterval) 后,JVM 尝试在安全点停止 Java 线程以进行定期清理,但在计数的循环完成之前无法执行此操作。
Thread.sleep
来自原生的方法returns,发现正在进行安全点操作,并挂起直到操作结束。
此时主线程正在等待循环完成 - 正是您所观察到的。
当您将休眠持续时间更改为 100 毫秒时,保证安全点发生在 Thread.sleep
returns 之后,因此该方法不会被阻止。
或者,如果您保留 1000 毫秒睡眠,但增加 -XX:GuaranteedSafepointInterval=2000
,主线程也不必等待。
修复
-XX:+UseCountedLoopSafepoints
选项关闭消除安全点轮询的优化。在这种情况下,Thread.sleep
将按预期休眠 1 秒。
此外,如果将 int i
更改为 long i
,循环将不再被视为计数,因此您不会看到提到的安全点效果。
自 JDK10 起,HotSpot 实施了 Loop Strip Mining 优化,解决了计数循环中安全点轮询的问题,而没有太多开销。因此,您的示例在 JDK 10 及更高版本中应该开箱即用。
在this issue的描述中可以找到很好的问题解释和解决方案。
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);
}
我想设置主线程休眠1000ms,但实际上输出会等到两个子线程计算结束后才输出,但是当我把时间调到100ms时,主线程会不等待子线程结束。
I want to set the main thread to sleep for 1000ms
你做到了。
but in fact the output will wait until the calculation of the two sub-threads ends before outputting
你有一台电脑;这并不神奇;它的资源是有限的。您的 2 个线程是 'busy-spinning' - 时不时地工作而不是睡觉。当然,您的线程将休眠 1000 毫秒。它可能不允许 运行 因为系统正忙于做其他事情(例如你的 2 个计算线程正在忙着旋转)。
如果您想等待一个线程完成,请调用 t1.join()
,它将休眠直到该线程完成。然后调用 t2.join()
,瞧。
如果您想让线程暂时不做任何事情,请使用一种机制让它休眠,例如 Thread.sleep
、wait/notifyAll
或 java.util.concurrent
包。比如轮询一个blockingqueue。
这是与 HotSpot 相关的重要效果 Safepoint mechanism。
背景
通常 HotSpot JVM 在循环内添加一个安全点轮询,以允许在 JVM 需要执行 stop-the-world 操作时暂停线程。安全点轮询不是免费的(即它有一些性能开销),因此 JIT 编译器会尽可能地尝试消除它。其中一项优化是从 counted loops.
中删除安全点轮询for (int i = 0; i < 1000000000; i++)
是一个典型的计数循环:它有一个单调整数循环变量(计数器)和有限的迭代次数。 JDK 8 JIT 在没有安全点轮询的情况下编译这样的循环。但是,这是一个很长的循环;需要几秒钟才能完成。虽然此循环是 运行,但 JVM 将无法停止线程。
HotSpot JVM 不仅将安全点用于 GC,还用于 -XX:GuaranteedSafepointInterval
选项控制,默认为 1000 毫秒。
你的例子发生了什么
- 你开始了两个不可中断的长循环(内部没有安全点检查)。
- 主线程休眠 1 秒。
- 1000 毫秒 (GuaranteedSafepointInterval) 后,JVM 尝试在安全点停止 Java 线程以进行定期清理,但在计数的循环完成之前无法执行此操作。
Thread.sleep
来自原生的方法returns,发现正在进行安全点操作,并挂起直到操作结束。
此时主线程正在等待循环完成 - 正是您所观察到的。
当您将休眠持续时间更改为 100 毫秒时,保证安全点发生在 Thread.sleep
returns 之后,因此该方法不会被阻止。
或者,如果您保留 1000 毫秒睡眠,但增加 -XX:GuaranteedSafepointInterval=2000
,主线程也不必等待。
修复
-XX:+UseCountedLoopSafepoints
选项关闭消除安全点轮询的优化。在这种情况下,Thread.sleep
将按预期休眠 1 秒。
此外,如果将 int i
更改为 long i
,循环将不再被视为计数,因此您不会看到提到的安全点效果。
自 JDK10 起,HotSpot 实施了 Loop Strip Mining 优化,解决了计数循环中安全点轮询的问题,而没有太多开销。因此,您的示例在 JDK 10 及更高版本中应该开箱即用。
在this issue的描述中可以找到很好的问题解释和解决方案。