java – 为什么最后一个线程没有被中断?

前端之家收集整理的这篇文章主要介绍了java – 为什么最后一个线程没有被中断?前端之家小编觉得挺不错的,现在分享给大家,也给大家做个参考。
我试图演示一个“随时算法” – 一种可以随时停止并返回其当前结果的算法.演示算法只返回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;
    }

在主程序中,我使用它像这样:

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();
    }
}

当我运行此程序时,我得到以下输入:

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

即,当我告诉他们时,前三个线程确实被中断,但是最后一个线程在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]");
    }

如果超时最多为800,则一切正常,并打印出类似“806.0 [ms]后超时”的内容.但如果超时为900,则打印“5084.0 [ms]后超时”.

编辑2:我的电脑有4个核心.该程序在Open JDK 8上运行.

解决方法

我可以确认这是一个HotSpot JVM错误.这是我对问题的初步分析.

@AdamSkywalker绝对正确地认为该问题与HotSpot HIT编译器中的安全点消除优化有关.虽然bug JDK-8154302看起来很相似,但事实上它是一个不同的问题.

什么是安全点问题

Safepoint是用于停止应用程序线程以执行需要stop-the-world pause的操作的JVM机制.HotSpot中的安全点是协作的,即应用程序线程定期检查它们是否需要停止.此检查通常发生在方法出口和内部循环中.

当然,这项检查不是免费的.因此,出于性能原因,JVM尝试消除冗余安全点轮询.其中一种优化是从计算的循环中删除安全点轮询 – 表单的循环

for (int i = 0; i < N; i++)

或同等学历.这里N是int类型的循环不变量.

通常这些循环是短暂运行的,但在某些情况下它们可能需要很长时间,例如,当N = 2_000_000_000时.安全点操作要求停止所有Java线程(不包括运行本机方法的线程).也就是说,单个长时间运行的计数循环可能会延迟整个安全点操作,而所有其他线程将等待此循环停止.

这正是070​​00中发生的事情.请注意

int l = 0;
    while (true) {
        if (++l == 0) ...
    }

只是表达232次迭代的计数循环的另一种方式.当Thread.sleep从本机函数返回并发现请求安全点操作时,它会停止并等待,直到长时间运行的计数循环也完成.这就是怪异延误的来源.

有一个任务来解决这个问题 – JDK-8186027.想法是将一个长循环分成两部分:

for (int i = 0; i < N; i += step) {
        for (int j = 0; j < step; j++) {
            // loop body
        }
        safepoint_poll();
    }

它尚未实现,但修复程序针对的是JDK 10.同时还有一个解决方法:JVM标志-XX:UseCountedLoopSafepoints也将强制安全点检查计数循环内部.

Thread.interrupted()有什么问题

我很确定Thread.sleep bug将作为Loop strip mining issue的副本关闭.您可以使用-XX:UseCountedLoopSafepoints选项验证此错误消失.

不幸的是,这个选项对原始问题没有帮助.我抓住原始问题中的算法挂起并查看正在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

这就是编译算法方法中的循环的方法.这里没有安全点调查,即使设置了-XX:UseCountedLoopSafepoints也是如此.

看起来安全点检查被错误地消除了,因为Thread.isInterrupted调用本应检查安全点本身.但是,Thread.isInterrupted是HotSpot的内在方法.这意味着没有真正的本机方法调用,但是JIT用一系列机器指令替换了对Thread.isInterrupted的调用,里面没有安全点检查.

我很快就会向Oracle报告这个错误.同时,解决方法是将循环计数器的类型从int更改为long.如果你将循环重写为

for (long i=0; i<n; ++i) { ...

不再有奇怪的延迟了.

原文链接:https://www.f2er.com/java/129482.html

猜你在找的Java相关文章