我有一个Java程序,它在一个单独的(非EDT)线程上执行紧密循环。虽然我认为Swing UI仍然应该响应,但事实并非如此。下面的示例程序显示了问题:单击“试用我”按钮应该在大约一半时间后弹出一个对话框,并且应该可以通过单击其任何响应立即关闭该对话框。相反,单击其中一个按钮后,对话框显示的时间会更长,和/或需要很长时间才能关闭。
有人知道为什么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(java可执行文件的-client
命令行参数)似乎可以在一台计算机上解决问题(更新2 ),而不是另一台。
更新3:点击按钮后,我看到Java进程的处理器使用率为200%,这意味着有2个处理器核心已满载。这对我来说根本没有意义。
更新4:也会在Windows上发生。
更新5:使用调试器(Eclipse)证明存在问题;调试器似乎无法阻止线程。这是非常不寻常的,我怀疑VM中存在某种活锁或竞争条件,所以我向Oracle提交了一个错误(评论ID JI-9029194)。
更新6:我找到了my bug report in the OpenJDK bug database。 (我没有被告知它已被接受,我不得不搜索它)。那里的讨论最有趣,并且已经解释了可能导致这个问题的原因。
答案 0 :(得分:2)
我看到了与Mac OS X相同的效果。虽然您的示例已正确同步,但您看到的平台/ JVM可变性可能是由于线程如何安排变幻莫测,导致饥饿。将Thread.yield()
添加到t
中的外部循环可以缓解此问题,如下所示。除了示例的人为性质之外,通常需要像Thread.yield()
这样的提示not。在任何情况下,请考虑SwingWorker
,显示here执行类似的紧密循环以进行演示。
尽管测试用例存在人为性质,但我认为在这种情况下根本不需要调用
Thread.yield()
。
正确;屈服只是暴露了潜在的问题:t
starves事件派遣线程。请注意,即使没有Thread.yield()
,GUI也会在下面的示例中立即更新。如本相关Q&A中所述,您可以尝试降低线程的优先级。或者,使用t
按照建议here在单独的JVM中运行ProcessBuilder
,SwingWorker
也可以在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);
}
}
的后台运行,如here所示。
但为什么?
所有支持的平台都基于单线程图形库构建。阻塞,饿死或饱和管理事件派遣线程相当容易。非平凡的后台任务通常会隐式产生,就像发布中间结果,阻塞I / O或等待工作队列一样。 不的任务可能必须明确产生。
<record id="action_liste_patients" model="ir.actions.act_window">
<field name="name">Patients</field>
<field name="res_model">hr.employee</field>
<field name="view_mode">kanban,tree,form</field>
<field name="domain">[('department_id.is_patients', '=', 'true')]</field>
<field name="context">{'create_as_patient': True}</field>
</record>
答案 1 :(得分:2)
用swingworker替换线程:
用swingworker替换线程,并在第一个for循环中做一些工作:
使用此代码,可以观察到预期的行为:
public class MFrame extends JFrame {
public static void main(String[] args) {
new MFrame();
}
public MFrame() {
JButton tryme = new JButton("Try me!");
tryme.addActionListener((e) -> {
SwingWorker<Void, Void> longProcess = new SwingWorker<Void, Void>() {
private StringBuilder sb = new StringBuilder();
@Override
protected Void doInBackground() throws Exception {
int a = 4;
for (int i = 0; i < 100000; i++) {
for (int j = 0; j < 100000; j++) {
a *= (i + j);
a += 7;
}
sb.append(a); // <-- this seems to be the key
}
System.out.println("a = " + a);
return null;
}
@Override
protected void done() {
try {
get();
System.out.println(sb.toString());
} catch (InterruptedException | ExecutionException e1) {
e1.printStackTrace();
}
}
};
longProcess.execute();
// 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
SwingUtilities.invokeLater(() -> JOptionPane.showConfirmDialog(this, "You should see this immediately"));
});
getContentPane().add(tryme);
pack();
setLocationRelativeTo(null);
setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
setVisible(true);
}
}
使用OP的原始代码进行相同的观察:
实施例
tryme.addActionListener((e) -> {
Thread t = new Thread(() -> {
StringBuilder sb = new StringBuilder();
int a = 4;
for (int i = 0; i < 100000; i++) {
for (int j = 0; j < 100000; j++) {
a *= (i + j);
a += 7;
}
sb.append(a); // <-- again the magic seems to be on this line
}
System.out.println(sb.toString());
});
...
});
我在一台半功能强大的机器上运行Ubuntu 14.04。
java version "1.8.0_72"
Java(TM) SE Runtime Environment (build 1.8.0_72-b15)
Java HotSpot(TM) 64-Bit Server VM (build 25.72-b15, mixed mode)
除了all之外没有多少没有丢失,有些人可能已经过多地优化了编译器,这使得它以某种方式阻止了UI线程。老实说,我不确定这一切意味着什么,但我确定有人会弄清楚
答案 2 :(得分:1)
默认情况下,从ActionListener
开始的所有代码执行包括Thread.sleep
后,所有从Thread.sleep
内部JOptionPane
开始的内容都会终止,并假设您丢失了所有活动包括绘画,在整个时间内完成这段代码,所有事件都在最后和一次绘制
此代码丢失了自动关闭Thread.sleep
,永远不会被绘制到屏幕上(模拟如何在Swing中轻松Runnable#Thread
杀死绘画)
Swing GUI对鼠标或键事件不负责任,不能终止此应用程序,这可以从SwingWorker
和Workers Thread
开始,这被指定为开始新的,Runnable#Thread
和SwingWorker
内的任何内容(Runnable#Thread
)在任务可取消期间(或使用run:
test started at - 16:41:13
Thread started at - 16:41:15
to test EDT before JOptionPane - true at 16:41:16
before JOptionPane at - 16:41:16
Thread ended at - 16:41:29
a = 1838603747
isEventDispatchThread()false
after JOptionPane at - 16:41:29
Thread started at - 16:41:34
to test EDT before JOptionPane - true at 16:41:34
before JOptionPane at - 16:41:34
Thread ended at - 16:41:47
a = 1838603747
isEventDispatchThread()false
after JOptionPane at - 16:41:47
BUILD SUCCESSFUL (total time: 38 seconds)
可以暂停,修改......)
这不是关于多线程,也不是要将资源管理器共享给另一个核心,在Win10中所有核心都显示我,按比例共享增量
从(稍加修改)代码输出(基于您的SSCCE / MCVE)
JOptionPane
再次自动关闭SwingWorker
永远不会被绘制到屏幕上(经过测试的win10-64b,i7,Java8),可能达到Java 1.6.022所有内容都将被绘制并且正确(AFAIK最后修复edt和来自这次import java.awt.Component;
import java.awt.EventQueue;
import java.awt.Window;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.text.SimpleDateFormat;
import java.util.Date;
import javax.swing.AbstractAction;
import javax.swing.Action;
import javax.swing.JButton;
import javax.swing.JDialog;
import javax.swing.JFrame;
import javax.swing.JOptionPane;
import javax.swing.SwingUtilities;
import javax.swing.Timer;
public class MFrame extends JFrame {
public static void main(String[] args) {
EventQueue.invokeLater(() -> {
new MFrame();
});
}
public MFrame() {
SimpleDateFormat sdf = new SimpleDateFormat("HH:mm:ss");
System.out.println("test started at - " + sdf.format(getCurrDate().getTime()));
//http://stackoverflow.com/a/18107432/714968
Action showOptionPane = new AbstractAction("show me pane!") {
private static final long serialVersionUID = 1L;
@Override
public void actionPerformed(ActionEvent e) {
createCloseTimer(3).start();
System.out.println("before JOptionPane at - "
+ sdf.format(getCurrDate().getTime()));
JOptionPane.showMessageDialog((Component) e.getSource(), "nothing to do!");
}
private Timer createCloseTimer(int seconds) {
ActionListener close = new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
Window[] windows = Window.getWindows();
for (Window window : windows) {
if (window instanceof JDialog) {
JDialog dialog = (JDialog) window;
if (dialog.getContentPane().getComponentCount() == 1
&& dialog.getContentPane().getComponent(0) instanceof JOptionPane) {
dialog.dispose();
System.out.println("after JOptionPane at - "
+ sdf.format(getCurrDate().getTime()));
}
}
}
}
};
Timer t = new Timer(seconds * 1000, close);
t.setRepeats(false);
return t;
}
};
JButton tryme = new JButton("Try me!");
tryme.addActionListener((e) -> {
System.out.println("Thread started at - "
+ sdf.format(getCurrDate().getTime()));
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("Thread ended at - "
+ sdf.format(getCurrDate().getTime()));
System.out.println("a = " + a);
System.out.println("isEventDispatchThread()" +
SwingUtilities.isEventDispatchThread());
});
t.start();
// Sleep to give the other thread a chance to get going:
try {
Thread.sleep(500);
} catch (InterruptedException ie) {
ie.printStackTrace();
}
// Now display a dialog
System.out.println("to test EDT before JOptionPane - "
+ SwingUtilities.isEventDispatchThread()
+ " at " + sdf.format(getCurrDate().getTime()));
showOptionPane.actionPerformed(e);
});
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
add(tryme);
pack();
setLocation(150, 150);
setVisible(true);
}
private Date getCurrDate() {
java.util.Date date = new java.util.Date();
return date;
}
}
无错误地工作)
Runnable#Thread
注意必须使用SwingWorker
和INSERT INTO dbo.rolls
( name, subject )
VALUES ( 'Jones', 'English'),
( 'Smith', 'Math'),
('Adams','English'),
('Adams', 'Math')
GO
;WITH CTE AS (
SELECT subquery1.name, 'B' AS code FROM (
SELECT name,COUNT(name) AS cnt
FROM rolls
WHERE subject = 'English' OR subject = 'Math'
GROUP BY name
HAVING COUNT(name) > 1 ) AS subquery1
UNION
SELECT subquery2.name, SUBSTRING(rolls.subject,1,1) AS code FROM (
SELECT name,COUNT(name) AS cnt
FROM rolls
WHERE subject = 'English' OR subject = 'Math'
GROUP BY name
HAVING COUNT(name) = 1 ) AS subquery2
INNER JOIN dbo.rolls
ON rolls.name = subquery2.name
)
SELECT * FROM CTE
进行测试
答案 3 :(得分:1)
这不是最终答案,但它更接近于理解问题。
我尝试使用sleep
和actionPerformed
最小化代码以消除潜在的陷阱,我相信我已经这样做了,同时保持问题的完整性:
public class MFrame extends JFrame {
public static void main(String[] args) {
EventQueue.invokeLater(() -> new MFrame());
}
public MFrame() {
Thread t = new Thread(() -> {
int a = 4;
for (int i = 0; i < 50000; i++) {
for (int j = 0; j < 50000; j++) {
a *= (i + j);
a += 7;
}
}
// System.out.println("c" + a);
});
System.out.println("a");
// pack();
t.start();
// pack();
System.out.println("b");
}
}
在Win7,i7 2670QM,JDK 1.8.0_25上我得到以下结果:
只有第二个pack
注释掉了:
a
b [pause]
c-1863004573
(预计即使没有同步,也会在打印b
之前达到打印c
,除非您使用的某些超级处理器可以更快地进行计算?)
只有第一个pack
注释掉了:
a [pause]
c-1863004573
b
(不是预期的)
您可以使用-client
标志确认我的结果吗?
答案 4 :(得分:1)
这似乎是一个问题。下面是观察Event Dispatcher Thread延迟处理,应该立即响应:
在Windows上
在步骤2和步骤3之间进行长时间延迟观察。
步骤3 - &gt;立即关闭对话框。
在Linux上
第2步到第3步 - 没有延迟。
步骤3 - &gt;很长时间关闭对话框。