Ere*_*evi 7 java multithreading jvm
我试图演示一个"随时算法" - 一种可以随时停止并返回其当前结果的算法.演示算法只返回i的一些数学函数,其中i正在增加.它会查看它是否被中断,如果是,则返回当前值:
static int algorithm(int n) {
int bestSoFar = 0;
for (int i=0; i<n; ++i) {
if (Thread.interrupted())
break;
bestSoFar = (int)Math.pow(i, 0.3);
}
return bestSoFar;
}
Run Code Online (Sandbox Code Playgroud)
在主程序中,我使用它像这样:
Runnable task = () -> {
Instant start = Instant.now();
int bestSoFar = algorithm(1000000000);
double durationInMillis = Duration.between(start, Instant.now()).toMillis();
System.out.println("after "+durationInMillis+" ms, the result is "+bestSoFar);
};
Thread t = new Thread(task);
t.start();
Thread.sleep(1);
t.interrupt();
t = new Thread(task);
t.start();
Thread.sleep(10);
t.interrupt();
t = new Thread(task);
t.start();
Thread.sleep(100);
t.interrupt();
t = new Thread(task);
t.start();
Thread.sleep(1000);
t.interrupt();
}
}
Run Code Online (Sandbox Code Playgroud)
当我运行此程序时,我得到以下输入:
after 0.0 ms, the result is 7
after 10.0 ms, the result is 36
after 100.0 ms, the result is 85
after 21952.0 ms, the result is 501
Run Code Online (Sandbox Code Playgroud)
也就是说,当我告诉他们时,前三个线程确实被打断了,但是最后一个线程在1秒后没有中断 - 它继续工作了将近22秒.为什么会这样?
编辑:我使用Future.get和超时获得类似的结果.在这段代码中:
Instant start = Instant.now();
ExecutorService executor = Executors.newCachedThreadPool();
Future<?> future = executor.submit(task);
try {
future.get(800, TimeUnit.MILLISECONDS);
} catch (TimeoutException e) {
future.cancel(true);
double durationInMillis = Duration.between(start, Instant.now()).toMillis();
System.out.println("Timeout after "+durationInMillis+" [ms]");
}
Run Code Online (Sandbox Code Playgroud)
如果超时最多为800,则一切正常,并打印出类似"806.0 [ms]后的超时".但如果超时为900,则打印"5084.0 [ms]后超时".
编辑2:我的电脑有4个核心.程序在Open JDK 8上运行.
我可以确认这是一个 HotSpot JVM 错误。这是我对问题的初步分析。
@AdamSkywalker 的假设是绝对正确的,即该问题与 HotSpot HIT 编译器中的安全点消除优化有关。虽然错误JDK-8154302看起来很相似,但实际上它是一个不同的问题。
Safepoint是一种 JVM 机制,用于停止应用程序线程以执行需要停止世界暂停的操作。HotSpot 中的安全点是协作的,即应用程序线程定期检查它们是否需要停止。此检查通常发生在方法出口和循环内部。
当然,这项检查并不是免费的。因此,出于性能原因,JVM 尝试消除冗余的安全点轮询。其中一种优化是从计数循环中删除安全点轮询 - 形式的循环
for (int i = 0; i < N; i++)
Run Code Online (Sandbox Code Playgroud)
或同等学历。这里 N 是类型的循环不变量int。
通常这些循环运行时间较短,但在某些情况下它们可能需要很长时间,例如当 N = 2_000_000_000 时。安全点操作要求停止所有Java 线程(不包括运行本机方法的线程)。也就是说,单个长时间运行的计数循环可能会延迟整个安全点操作,并且所有其他线程将等待该循环停止。
这正是JDK-8154302中发生的情况。注意
int l = 0;
while (true) {
if (++l == 0) ...
}
Run Code Online (Sandbox Code Playgroud)
只是表达 2 32 次迭代的计数循环的另一种方式。当Thread.sleep从本机函数返回并发现请求安全点操作时,它会停止并等待,直到长时间运行的计数循环也完成。这就是奇怪的延迟的来源。
有一个任务可以解决这个问题 - JDK-8186027。这个想法是将一个长循环分成两部分:
for (int i = 0; i < N; i += step) {
for (int j = 0; j < step; j++) {
// loop body
}
safepoint_poll();
}
Run Code Online (Sandbox Code Playgroud)
它尚未实现,但修复针对的是 JDK 10。同时有一个解决方法:JVM 标志-XX:+UseCountedLoopSafepoints也会强制在计数循环内进行安全点检查。
我非常确定Thread.sleep 错误将作为循环条带挖掘问题的重复项而被关闭。您可以验证该错误是否会通过选项消失-XX:+UseCountedLoopSafepoints。
不幸的是,这个选项对解决原来的问题没有帮助。我抓住了原始问题挂起的那一刻algorithm,并查看了在 gdb 下执行的代码:
loop_begin:
0x00002aaaabe903d0: mov %ecx,%r11d
0x00002aaaabe903d3: inc %r11d ; i++
0x00002aaaabe903d6: cmp %ebp,%r11d ; if (i >= n)
0x00002aaaabe903d9: jge 0x2aaaabe90413 ; break;
0x00002aaaabe903db: mov %ecx,%r8d
0x00002aaaabe903de: mov %r11d,%ecx
0x00002aaaabe903e1: mov 0x1d0(%r15),%rsi ; rsi = Thread.current();
0x00002aaaabe903e8: mov 0x1d0(%r15),%r10 ; r10 = Thread.current();
0x00002aaaabe903ef: cmp %rsi,%r10 ; if (rsi != r10)
0x00002aaaabe903f2: jne 0x2aaaabe903b9 ; goto slow_path;
0x00002aaaabe903f4: mov 0x128(%r15),%r10 ; r10 = current_os_thread();
0x00002aaaabe903fb: mov 0x14(%r10),%r11d ; isInterrupted = r10.interrupt_flag;
0x00002aaaabe903ff: test %r11d,%r11d ; if (!isInterrupted)
0x00002aaaabe90402: je 0x2aaaabe903d0 ; goto loop_begin
Run Code Online (Sandbox Code Playgroud)
这就是循环方法的algorithm编译方式。这里没有安全点轮询,即使-XX:+UseCountedLoopSafepoints设置了也是如此。
看起来安全点检查被错误地消除了,因为Thread.isInterrupted应该检查安全点本身的调用。然而,Thread.isInterrupted是HotSpot内在的方法。这意味着没有真正的本机方法调用,但 JITThread.isInterrupted用一系列机器指令替换了调用,内部没有安全点检查。
我很快就会向 Oracle 报告该错误。同时,解决方法是将循环计数器的类型从 更改int为long。如果将循环重写为
for (long i=0; i<n; ++i) { ...
Run Code Online (Sandbox Code Playgroud)
不会再有奇怪的延误了。
| 归档时间: |
|
| 查看次数: |
174 次 |
| 最近记录: |