gpt4 book ai didi

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

转载 作者:塔克拉玛干 更新时间:2023-11-03 03:20:25 24 4
gpt4 key购买 nike

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

  • 问题出现在 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 调用屈服于另一个线程,因为 如上所述,有备用处理器内核;在调用 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 可执行文件的命令行参数)似乎可以抑制问题(更新 2)在一台机器上而不是另一台 .

更新 3:单击按钮后,我看到 Java 进程使用了​​ 200% 的处理器,这意味着有 2 个处理器内核已满载。这对我来说根本没有意义。

更新 4: 也发生在 Windows 上。

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

更新 6: 我找到了 my bug report in the OpenJDK bug database . (我没有被告知它已被接受,我不得不搜索它)。那里的讨论最有趣,并且已经阐明了导致此问题的可能原因。

最佳答案

我在 Mac OS X 上看到了相同的效果。虽然您的示例已正确同步,但您看到的平台/JVM 可变性可能是由于线程调度方式的变化无常,导致饥饿。添加Thread.yield()t 中的外循环可以缓解这个问题,如下所示。除了示例的人为性质,像 Thread.yield() 这样的提示将 not通常需要。无论如何,请考虑 SwingWorker , 如图所示 here出于演示目的执行类似的紧密循环。

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.

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

but why?

所有支持的平台都建立在单线程图形库上。阻塞、饿死或饱和管理事件分派(dispatch)线程是相当容易的。重要的后台任务通常会隐式产生,例如在发布中间结果、阻塞 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);
}
}

关于java - 其他线程中的忙循环延迟了 EDT 处理,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/35154352/

24 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com