草庐IT

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

coder 2024-03-20 原文

我正在尝试演示一种“随时算法”——一种可以随时停止并返回其当前结果的算法。演示算法仅返回 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 编译器中的安全点消除优化有关。尽管错误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 线程(不包括运行 native 方法的线程)。也就是说,一个长时间运行的计数循环可能会延迟整个安全点操作,所有其他线程将等待这个循环停止。

这正是 JDK-8154302 中发生的事情.注意

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

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

有一个任务可以解决这个问题 - 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 选项验证此错误是否消失。

不幸的是,这个选项对解决原来的问题没有帮助。我捕获了原始问题中 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

algorithm 方法中的循环是这样编译的。这里没有安全点轮询,即使设置了 -XX:+UseCountedLoopSafepoints

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

我会尽快将错误报告给 Oracle。同时,解决方法是将循环计数器的类型从 int 更改为 long。如果将循环重写为

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

不会再有奇怪的延迟了。

关于java - 为什么最后一个线程没有被中断?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/47500446/

有关java - 为什么最后一个线程没有被中断?的更多相关文章

  1. ruby - 为什么我可以在 Ruby 中使用 Object#send 访问私有(private)/ protected 方法? - 2

    类classAprivatedeffooputs:fooendpublicdefbarputs:barendprivatedefzimputs:zimendprotecteddefdibputs:dibendendA的实例a=A.new测试a.foorescueputs:faila.barrescueputs:faila.zimrescueputs:faila.dibrescueputs:faila.gazrescueputs:fail测试输出failbarfailfailfail.发送测试[:foo,:bar,:zim,:dib,:gaz].each{|m|a.send(m)resc

  2. ruby-on-rails - Rails - 子类化模型的设计模式是什么? - 2

    我有一个模型:classItem项目有一个属性“商店”基于存储的值,我希望Item对象对特定方法具有不同的行为。Rails中是否有针对此的通用设计模式?如果方法中没有大的if-else语句,这是如何干净利落地完成的? 最佳答案 通常通过Single-TableInheritance. 关于ruby-on-rails-Rails-子类化模型的设计模式是什么?,我们在StackOverflow上找到一个类似的问题: https://stackoverflow.co

  3. ruby - 什么是填充的 Base64 编码字符串以及如何在 ruby​​ 中生成它们? - 2

    我正在使用的第三方API的文档状态:"[O]urAPIonlyacceptspaddedBase64encodedstrings."什么是“填充的Base64编码字符串”以及如何在Ruby中生成它们。下面的代码是我第一次尝试创建转换为Base64的JSON格式数据。xa=Base64.encode64(a.to_json) 最佳答案 他们说的padding其实就是Base64本身的一部分。它是末尾的“=”和“==”。Base64将3个字节的数据包编码为4个编码字符。所以如果你的输入数据有长度n和n%3=1=>"=="末尾用于填充n%

  4. ruby - 解析 RDFa、微数据等的最佳方式是什么,使用统一的模式/词汇(例如 schema.org)存储和显示信息 - 2

    我主要使用Ruby来执行此操作,但到目前为止我的攻击计划如下:使用gemsrdf、rdf-rdfa和rdf-microdata或mida来解析给定任何URI的数据。我认为最好映射到像schema.org这样的统一模式,例如使用这个yaml文件,它试图描述数据词汇表和opengraph到schema.org之间的转换:#SchemaXtoschema.orgconversion#data-vocabularyDV:name:namestreet-address:streetAddressregion:addressRegionlocality:addressLocalityphoto:i

  5. ruby - 难道Lua没有和Ruby的method_missing相媲美的东西吗? - 2

    我好像记得Lua有类似Ruby的method_missing的东西。还是我记错了? 最佳答案 表的metatable的__index和__newindex可以用于与Ruby的method_missing相同的效果。 关于ruby-难道Lua没有和Ruby的method_missing相媲美的东西吗?,我们在StackOverflow上找到一个类似的问题: https://stackoverflow.com/questions/7732154/

  6. ruby - 使用 Vim Rails,您可以创建一个新的迁移文件并一次性打开它吗? - 2

    使用带有Rails插件的vim,您可以创建一个迁移文件,然后一次性打开该文件吗?textmate也可以这样吗? 最佳答案 你可以使用rails.vim然后做类似的事情::Rgeneratemigratonadd_foo_to_bar插件将打开迁移生成的文件,这正是您想要的。我不能代表textmate。 关于ruby-使用VimRails,您可以创建一个新的迁移文件并一次性打开它吗?,我们在StackOverflow上找到一个类似的问题: https://sta

  7. ruby-on-rails - Rails - 一个 View 中的多个模型 - 2

    我需要从一个View访问多个模型。以前,我的links_controller仅用于提供以不同方式排序的链接资源。现在我想包括一个部分(我假设)显示按分数排序的顶级用户(@users=User.all.sort_by(&:score))我知道我可以将此代码插入每个链接操作并从View访问它,但这似乎不是“ruby方式”,我将需要在不久的将来访问更多模型。这可能会变得很脏,是否有针对这种情况的任何技术?注意事项:我认为我的应用程序正朝着单一格式和动态页面内容的方向发展,本质上是一个典型的网络应用程序。我知道before_filter但考虑到我希望应用程序进入的方向,这似乎很麻烦。最终从任何

  8. ruby-on-rails - 渲染另一个 Controller 的 View - 2

    我想要做的是有2个不同的Controller,client和test_client。客户端Controller已经构建,我想创建一个test_clientController,我可以使用它来玩弄客户端的UI并根据需要进行调整。我主要是想绕过我在客户端中内置的验证及其对加载数据的管理Controller的依赖。所以我希望test_clientController加载示例数据集,然后呈现客户端Controller的索引View,以便我可以调整客户端UI。就是这样。我在test_clients索引方法中试过这个:classTestClientdefindexrender:template=>

  9. ruby - 为什么 4.1%2 使用 Ruby 返回 0.0999999999999996?但是 4.2%2==0.2 - 2

    为什么4.1%2返回0.0999999999999996?但是4.2%2==0.2。 最佳答案 参见此处:WhatEveryProgrammerShouldKnowAboutFloating-PointArithmetic实数是无限的。计算机使用的位数有限(今天是32位、64位)。因此计算机进行的浮点运算不能代表所有的实数。0.1是这些数字之一。请注意,这不是与Ruby相关的问题,而是与所有编程语言相关的问题,因为它来自计算机表示实数的方式。 关于ruby-为什么4.1%2使用Ruby返

  10. ruby-on-rails - rails 目前在重启后没有安装 - 2

    我有一个奇怪的问题:我在rvm上安装了ruby​​onrails。一切正常,我可以创建项目。但是在我输入“railsnew”时重新启动后,我有“程序'rails'当前未安装。”。SystemUbuntu12.04ruby-v"1.9.3p194"gemlistactionmailer(3.2.5)actionpack(3.2.5)activemodel(3.2.5)activerecord(3.2.5)activeresource(3.2.5)activesupport(3.2.5)arel(3.0.2)builder(3.0.0)bundler(1.1.4)coffee-rails(

随机推荐