JComponent.paintImmediately()如何在Java Swing中工作?

我的理解:与Swing中的大多数组件/操作不同,对JComponent.repaint()的调用是线程安全的,即虽然重新绘制请求来自另一个线程(即不是来自EDT),但实际绘制仅在EDT中进行。 下面的代码片段演示了这一点。

public class PaintingDemo { public static void main(String[] args) { final JFrame frame = new JFrame(); final JPanel p = new MyPanel(); SwingUtilities.invokeLater(new Runnable() { @Override public void run() { frame.add(p, BorderLayout.CENTER); frame.setSize(200, 200); frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); frame.setVisible(true); } }); new Thread("MyThread") { public void run() { while (true) { // Below statements are important to show the difference p.repaint(); p.paintImmediately(p.getBounds()); try { Thread.sleep(1000); } catch(Exception e) {} } } }.start(); } } class MyPanel extends JPanel { @Override public void paint(Graphics g) { System.out.println("paint() called in "+ Thread.currentThread().getName()); super.paint(g); } } 

从输出中可以看出,当调用repaint()时,绘制是在EDT中完成的,无论调用哪个线程 – 所以没有问题。 但是,在paintImmediately()的情况下 – 绘制发生在调用它的同一个线程中。

考虑一种情况,其中EDT正在改变组件的状态,另一个线程(从中调用paintImmediately())正在绘制相同的组件。

我的问题:在paintImmediately()的情况下,如何处理Event Dispatcher Thread(EDT)和其他线程之间的同步?

据我所知,当你调用paintImmediately时,你调用以下代码:

  Component c = this; Component parent; if(!isShowing()) { return; } JComponent paintingOigin = SwingUtilities.getPaintingOrigin(this); if (paintingOigin != null) { Rectangle rectangle = SwingUtilities.convertRectangle( c, new Rectangle(x, y, w, h), paintingOigin); paintingOigin.paintImmediately(rectangle.x, rectangle.y, rectangle.width, rectangle.height); return; } while(!c.isOpaque()) { parent = c.getParent(); if(parent != null) { x += c.getX(); y += c.getY(); c = parent; } else { break; } if(!(c instanceof JComponent)) { break; } } if(c instanceof JComponent) { ((JComponent)c)._paintImmediately(x,y,w,h); } else { c.repaint(x,y,w,h); } 

所以,除非这不是一个JComponent ,你最终调用_paintImmediately()最终调用paint(Graphics)如下面的堆栈跟踪所示(从我将在本文末尾发布的一段代码中捕获):

 Thread [pool-1-thread-1] (Suspended) TestPaint$1.paint(Graphics) line: 23 TestPaint$1(JComponent).paintToOffscreen(Graphics, int, int, int, int, int, int) line: 5221 RepaintManager$PaintManager.paintDoubleBuffered(JComponent, Image, Graphics, int, int, int, int) line: 1482 RepaintManager$PaintManager.paint(JComponent, JComponent, Graphics, int, int, int, int) line: 1413 RepaintManager.paint(JComponent, JComponent, Graphics, int, int, int, int) line: 1206 TestPaint$1(JComponent)._paintImmediately(int, int, int, int) line: 5169 TestPaint$1(JComponent).paintImmediately(int, int, int, int) line: 4980 TestPaint$1(JComponent).paintImmediately(Rectangle) line: 4992 TestPaint$3.run() line: 50 ThreadPoolExecutor.runWorker(ThreadPoolExecutor$Worker) line: 1110 ThreadPoolExecutor$Worker.run() line: 603 Thread.run() line: 722 

但是如果你试图同时调用repaint() (来自另一个Thread),你会看到两个同时运行(我尝试使用debuger踩入代码并且绘画从未停止在另一个Thread中发生)它似乎在Java代码级别,没有太多同步(至少我无法发现任何东西)。 因此,如果您最终修改了EDT中的组件状态,我相信结果是非常不可预测的,您应该通过各种方式避免这种情况。

为了说明我的观点,我尝试在paint方法中修改变量的状态,添加一个sleep以增加2个线程(EDT和其他)之间发生冲突的风险,并且看起来两者之间没有同步。线程( System.err.println()不时输出null )。

现在我想知道为什么你需要立即执行paint。 除非你阻止EDT,否则没有那么多正当理由来执行这样的事情。

下面是我用来测试这些东西的代码(非常接近问题中发布的代码)。 代码只是试图了解正在发生的事情,而不是展示如何进行正确的绘画或任何良好的Swing练习。

 import java.awt.Color; import java.awt.Graphics; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.util.Random; import java.util.concurrent.Executors; import javax.swing.JFrame; import javax.swing.JPanel; import javax.swing.SwingUtilities; import javax.swing.Timer; public class TestPaint { protected void initUI() { JFrame frame = new JFrame(); frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); frame.setTitle(TestPaint.class.getSimpleName()); final Random rand = new Random(); final JPanel comp = new JPanel() { private String value; @Override public void paint(Graphics g) { value = "hello"; super.paint(g); try { Thread.sleep(20); } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } g.setColor(new Color(rand.nextInt(256), rand.nextInt(256), rand.nextInt(256))); g.fillRect(0, 0, getWidth(), getHeight()); if (SwingUtilities.isEventDispatchThread()) { System.err.println("Painting in the EDT " + getValue()); } else { System.err.println("Not painting in EDT " + getValue()); } value = null; } public String getValue() { return value; } }; frame.add(comp); frame.setSize(400, 400); frame.setLocationRelativeTo(null); frame.setVisible(true); Timer t = new Timer(1, new ActionListener() { @Override public void actionPerformed(ActionEvent e) { comp.repaint(); } }); t.start(); Executors.newSingleThreadExecutor().execute(new Runnable() { @Override public void run() { while (true) { comp.paintImmediately(comp.getBounds()); } } }); } public static void main(String[] args) { SwingUtilities.invokeLater(new Runnable() { @Override public void run() { new TestPaint().initUI(); } }); } }