有 Java 编程相关的问题?

你可以在下面搜索框中键入要查询的问题!

其他线程中的java忙循环延迟了EDT处理

我有一个Java程序,它在一个单独的(非EDT)线程上执行紧循环。尽管我认为Swing UI应该仍然具有响应性,但事实并非如此。下面的示例程序显示了这个问题:单击“Try me”按钮应该会在半秒钟后弹出一个对话框,并且应该可以通过单击对话框的任何响应立即关闭该对话框。相反,对话框需要更长的时间才能显示,和/或在单击其中一个按钮后需要很长时间才能关闭

  • 问题发生在Linux(两台不同的机器,具有不同的发行版)、Windows、Raspberry Pi(仅限于服务器VM)和Mac OS X(另一位SO用户报告)上
  • Java版本1.8.0_65和1.8.0_72(都试用过)
  • i7处理器具有多个内核。EDT应有足够的备用处理能力

有人知道为什么EDT处理会被延迟,即使只有一个繁忙的线程吗

(请注意,尽管有各种各样的建议认为Thread.sleep呼叫是问题的原因,但事实并非如此。它可以被删除,问题仍然可以重现,尽管它表现得稍微不那么频繁,并且通常表现出上述第二种行为——即无响应JOptionPane而不是延迟对话框的出现。此外,没有理由认为睡眠调用应该让位给另一个线程,因为如上所述,有备用处理器核;调用sleep后,EDT可以继续在另一个内核上运行

import java.awt.EventQueue;

import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JOptionPane;

public class MFrame extends JFrame
{
    public static void main(String[] args)
    {
        EventQueue.invokeLater(() -> {
            new MFrame();
        });
    }

    public MFrame()
    {
        JButton tryme = new JButton("Try me!");

        tryme.addActionListener((e) -> {
            Thread t = new Thread(() -> {
                int a = 4;
                for (int i = 0; i < 100000; i++) {
                    for (int j = 0; j < 100000; j++) {
                        a *= (i + j);
                        a += 7;
                    }
                }
                System.out.println("a = " + a);
            });

            t.start();

            // Sleep to give the other thread a chance to get going.
            // (Included because it provokes the problem more reliably,
            // but not necessary; issue still occurs without sleep call).
            try {
                Thread.sleep(500);
            }
            catch (InterruptedException ie) {
                ie.printStackTrace();
            }

            // Now display a dialog
            JOptionPane.showConfirmDialog(null, "You should see this immediately");
        });

        getContentPane().add(tryme);

        pack();
        setVisible(true);
    }
}

更新:问题只发生在服务器VM上(但请参阅进一步更新)。将客户机VM(-client命令行参数指定为java可执行文件)似乎可以在一台机器上抑制问题(update 2,但在另一台机器上却不能

更新3:单击按钮后,我看到Java进程使用了200%的处理器,这意味着有两个处理器内核已完全加载。这对我来说毫无意义

更新4:在Windows上也会出现

更新5:使用调试器(Eclipse)证明是有问题的;调试器似乎无法停止线程。这是非常不寻常的,我怀疑VM中存在某种活锁或竞争条件,所以我向Oracle提交了一个bug(查看ID JI-9029194)

更新6:我找到了my bug report in the OpenJDK bug database。(我没有被告知它已被接受,我不得不搜索它)。那里的讨论非常有趣,已经揭示了这个问题的原因


共 (1) 个答案

  1. # 1 楼答案

    我在Mac OS X上也看到了同样的效果。虽然您的示例已正确同步,但您看到的平台/JVM可变性很可能是由于线程调度方式的变化无常,导致了饥饿。将^{}添加到t中的外部循环可以缓解问题,如下所示。除了示例的人工性质之外,通常需要Thread.yield()这样的提示。在任何情况下,考虑^{},如{{a4}〉,为了演示目的,执行类似的紧循环。

    I do not believe that Thread.yield() should need to be called in this case at all, despite the artificial nature of the test case, however.

    正确;屈服只会暴露出潜在的问题:tstarves事件调度线程。请注意,在下面的示例中,GUI会迅速更新,即使没有Thread.yield()。正如在相关的Q&A中所讨论的,您可以尝试降低线程的优先级。或者,在单独的JVM中运行t,如建议的here使用ProcessBuilder,也可以在SwingWorker的后台运行,如图here

    but why?

    所有受支持的平台都构建在单线程图形库上。很容易阻塞、饿死或使管理事件调度线程饱和。非平凡的后台任务通常会隐式地产生结果,比如发布中间结果、阻塞I/O或等待工作队列时。一项任务如果不是所做的,就必须明确地做出让步

    image

    import java.awt.EventQueue;
    import javax.swing.JButton;
    import javax.swing.JFrame;
    
    public class MFrame extends JFrame {
    
        private static final int N = 100_000;
        private static final String TRY_ME = "Try me!";
        private static final String WORKING = "Working…";
    
        public static void main(String[] args) {
            EventQueue.invokeLater(new MFrame()::display);
        }
    
        private void display() {
            JButton tryme = new JButton(TRY_ME);
            tryme.addActionListener((e) -> {
                Thread t = new Thread(() -> {
                    int a = 4;
                    for (int i = 0; i < N; i++) {
                        for (int j = 0; j < N; j++) {
                            a *= (i + j);
                            a += 7;
                        }
                        Thread.yield();
                    }
                    EventQueue.invokeLater(() -> {
                        tryme.setText(TRY_ME);
                        tryme.setEnabled(true);
                    });
                    System.out.println("a = " + a);
                });
                t.start();
                tryme.setEnabled(false);
                tryme.setText(WORKING);
            });
            add(tryme);
            pack();
            setLocationRelativeTo(null);
            setDefaultCloseOperation(EXIT_ON_CLOSE);
            setVisible(true);
        }
    }