My understanding: Unlike most of the components/operations in Swing call to JComponent.repaint() is thread-safe i.e. though a repaint request is made from another thread (i.e. not from EDT), the actual painting happens in EDT only. Below code snippet demonstrates this.
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);
}
}
From the output it is known that painting is done in EDT when repaint() is invoked irrespective of from which thread it is called - so no issues. But, in the case of paintImmediately() - painting happens in the same thread from which it is called.
Consider a case where EDT is changing the state of a component and another thread (from which paintImmediately() is invoked) is painting the same component.
My Question: In case of paintImmediately(), how is synchronization between Event Dispatcher Thread (EDT) and other threads handled?
To my understanding, when you call paintImmediately, you call the following code:
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);
}
So, unless this is not a JComponent
, you end up calling _paintImmediately()
which ends up calling paint(Graphics)
as suggests the stack trace below (captured from a piece of code I will post at the end of this post):
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
But if you try to also call repaint()
simultaneously (from another Thread), you see that both run at the same time (I tried stepping in the code with debuger and painting never stopped occuring in the other Thread) it seems that at the Java code level, there is not much synchronization (at least I could not spot anything). So if you end up modifying the component state in the EDT, I believe that results are quite unpredictable and you should avoid such situation by all means.
Just to illustrate my point, I tried modifying the state of a variable within the paint
method, add a sleep
to increase the risk of collisions between the 2 Threads (EDT and the other) and it obvisouly seems that there is no synchronization between the two Threads (the System.err.println()
outputted null
from time to time).
Now I would wonder why you need to perform a paintImmediately. Unless you are blocking the EDT, there is not so many valid reasons to perform such thing.
Below is the code I used to test those things (pretty close the one posted in the question). The code is only meant to try to understand what is going on, not to show how to perform proper painting nor any good Swing practice.
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();
}
});
}
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With