线程睡眠在actionPerformed方法中

首先,我想说我知道这种方法是错误的所以我问这个问题是因为纯粹的好奇心。 可以说我有一个像这样的swing应用程序:

import java.awt.BorderLayout; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import javax.swing.ImageIcon; import javax.swing.JButton; import javax.swing.JFrame; import javax.swing.JLabel; import javax.swing.JPanel; import javax.swing.SwingUtilities; public class ThreadSleeping { JFrame frame = new JFrame(); JPanel panel = new JPanel(); JButton button = new JButton("Load"); JLabel label = new JLabel(); public ThreadSleeping() { panel.add(button); button.addActionListener(new ActionListener() { @Override public void actionPerformed(ActionEvent arg0) { label.setIcon(new ImageIcon( "C:/Users/Public/Pictures/Sample Pictures/Tulips.jpg")); System.out.println("Tulips painted"); try { Thread.sleep(2000); } catch (InterruptedException e) { e.printStackTrace(); } label.setIcon(new ImageIcon( "C:/Users/Public/Pictures/Sample Pictures/Koala.jpg")); System.out.println("Koala painted"); } }); frame.add(panel, BorderLayout.NORTH); frame.add(label, BorderLayout.CENTER); frame.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE); frame.setSize(1024, 768); // frame.pack(); frame.setVisible(true); } public static void main(String[] args) { SwingUtilities.invokeLater(new Runnable() { @Override public void run() { new ThreadSleeping(); } }); } } 

基本上当我点击Load按钮时,我希望显示Tulips.jpg图像,然后GUI冻结2秒钟,之后我希望显示Koala.jpg图像。 但是会发生的是:我点击按钮,GUI冻结了2秒钟,并显示Koala.jpg 。 之前没有Tulips.jpg 。 但令我困惑的是当我把那些System.out.println("Tulips painted");System.out.println("Koala painted"); 。 因此,当我点击按钮时,它会打印“郁金香画”,并在2秒后“考拉画”。 谁能告诉我这里发生了什么? 问候。

  1. 在这种情况下工作, 因为你以编程方式冻结了Swou GUI,但是有/不是另一个更新,而是另一个JComponent(s)

  2. 在没有更新到Swing GUI的情况下,dons’t工作,Thread.sleep(int)冻结事件调度线程,

  3. 默认情况下,对JComponents XxxModels所有更新JComponents XxxModels不会在JComponents view可见

  4. 例如,直到睡眠结束,你将失去所有更新到GUI

我打算在评论中提出的观点:

如果你睡觉了edt,那么产生的错误行为基本上是不可预测的。

从某种意义上讲是不可预知的,你无法知道会发生什么。 我们所能做的就是猜…

技术上的原因是,大多数ui更新不是立即发生的,而是安排的:我们无法真正知道在我们后面排队的是什么。 请记住:它只是一条通道,当它在actionPerformed中时,我们坐在它里面。

出于教育原因,下面的代码是原始代码,有几行代表/评论以演示不同的场景。

  • [0]在睡觉之前/之后重置图标:正如您已经注意到的那样,即使拍摄了该属性 ,第一个也不显示。 技术原因:视觉更新通过label.repaint()发生,该label.repaint()在EDT上安排进行后续处理(如其api doc所述)
  • [1]浏览api文档,我们注意到另一种方法: paintImmediately(...)被记录为完全按照它的名字所说和允许 – 正如我们在EDT上 – 被允许被调用。 看起来成功,黄色图标显示出来。
  • [2]但是等待:在边界线的中心,标签无论如何填充该区域,无论它是否有图标。 让我们试着把它放到一个需要重新布局的区域,就像进入南方的那样。 回到正方形[0],黄色图标不显示。
  • [3]查看setIcon(..)的来源显示布局是……再次安排。 我们在square [1]中了解到,如果布局成为invalidate() / validate() ,我们可以立即强制发生。 宾果,黄色图标,即使在南方。
  • [4]令人讨厌的子类,它安排了图标属性设置(注意:虽然这里设计的合同中没有任何内容妨碍子类做到这一点!)。 由于属性没有设置,黄色没有显示,回到正方形[0]

在一天结束时(但在睡觉之前,EDT :-),根本无法可靠地预测睡眠期间的视觉结果。 视觉效果只是冰的一角……

 /** * Unpredictable behaviour when sleeping the EDT. * http://stackoverflow.com/q/15600203/203657 * * [0] run as-is: property set but yellow not showing * [1] uncomment paintImmediately: yellow showing in center * [2] add label to south: yellow not showing * [3] force immediate in-/validation: yellow showing in south * [4] subclass label with invoked property setting: * property not set, yellow not showing * */ public class ThreadSleeping { JFrame frame = new JFrame(); JPanel panel = new JPanel(); JButton button = new JButton("Load"); JLabel label = new JLabel() { // [4] subclass implemented to schedule the property setting // @Override // public void setIcon(final Icon icon) { // SwingUtilities.invokeLater(new Runnable() { // public void run() { // superSetIcon(icon); // // } // }); // } // // protected void superSetIcon(Icon icon) { // super.setIcon(icon); // } // }; public ThreadSleeping() { panel.add(button); button.addActionListener(new ActionListener() { @Override public void actionPerformed(ActionEvent arg0) { Icon firstIcon = new FixedIcon(Color.YELLOW, 100, 100); Icon secondIcon = new FixedIcon(Color.RED, 500, 500); label.setIcon(firstIcon); // check if the property is already set System.out.println(label.getIcon()); // following lines try to force the output before going to sleep // [3] paintImmediately + force immediate re-layout // label.invalidate(); // label.getParent().validate(); // {1] paintImmediately (fine for center, no effect for south) // ((JComponent) label.getParent()).paintImmediately(0, 0, 5000, 5000); try { Thread.sleep(2000); } catch (InterruptedException e) { e.printStackTrace(); } label.setIcon(secondIcon); } }); frame.add(panel, BorderLayout.NORTH); // un/comment one of the following, placing the // label either in CENTER (= sized to fill) frame.add(label, BorderLayout.CENTER); // [2] or in SOUTH (= sized to pref) // frame.add(label, BorderLayout.SOUTH); frame.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE); frame.setSize(1024, 768); frame.setVisible(true); } /** * Simple fixed size Icon filling its area. */ public static class FixedIcon implements Icon { private Color background; private int width; private int height; public FixedIcon(Color background, int width, int height) { this.background = background; this.width = width; this.height = height; } @Override public void paintIcon(Component c, Graphics g, int x, int y) { g.setColor(background); g.fillRect(0, 0, width, height); } @Override public int getIconWidth() { return width; } @Override public int getIconHeight() { return height; } @Override public String toString() { return "[" + background + " w/h " + width + "/" + height + "]"; } } public static void main(String[] args) { SwingUtilities.invokeLater(new Runnable() { @Override public void run() { new ThreadSleeping(); } }); } }