In this section we take a look at a naive GUI implementation that shows how Swing freezes in case the application programmer doesn't take any special actions against it. We also describe the problem of calls into Swing components triggered by any other thread than the event dispatch thread (EDT).
Swing is not designed for multi threading, so let us first recall the single threading rule of every Swing GUI:
Access to a (visible) Swing component has to occur in the event dispatch thread.
The EDT is responsible to process all GUI related events, e.g. notifying listeners of user input, repainting dirty regions or updated areas. All these events are enqueued and treated sequentially - if one of them takes a long time to be processed, all further events will have to wait.
Throughout the next sections we'll put the code of a Swing GUI component and a non visual bean side by side. The bean encapsulates an extensive calculation that is used by multiple threads. Code run on the EDT is shown in green and code called by any other thread is shown in red.
As you can see in the following code, GUI.java
calls the method getValue()
on the bean when an action is performed. The EDT is routed from the GUI to the bean. While it is performing its calculations no further swing events can be processed - the GUI freezes.
One of these queued events is the repaint of the label triggered by label.setText("...")
. When getValue()
returns, the text of the label is changed again before the previous text was painted. So in fact "..."
is never seen:
public void actionPerformed(ActionEvent e)
{
label.setText("...");
label.setText(bean.getValue());
}
public void propertyChange(PropertyChangeEvent ev)
{
label.setText((String)ev.getNewValue());
}
public String getValue()
{
String value;
// extensive calculation
return value;
}
public void setValue(String value)
{
this.value = value;
firePropertyChange(value);
}
What happens if setValue()
is invoked on the bean on another thread. The listeners are notified (the class GUI
implements java.beans.PropertyChangeListener
and is registered as a listener for changes of the bean), triggering a call to propertyChange()
on the GUI. The text of the label is altered on the calling thread, violating the Swing threading rule.
The color distribution gives a hint where to search for problems of this implementation:
Green lines of code in BeanImpl.java
result in a GUI freeze, red lines in GUI.java
show a violation to the Swing threading rule.
One obvious solution to the problems seen in the previous section is to shift the invocation of getValue()
from the EDT to a separate thread. When this method returns we must not propagate the result to a Swing component though. We have to return control to the EDT instead. This can be achieved via SwingUtilities.invokeLater()
which will use our Runnable to correctly change the label's text on the EDT:
public void actionPerformed(ActionEvent e)
{
label.setText("...");
new Thread(new Runnable()
{
public void run()
{
final String value = bean.getValue();
SwingUtilities.invokeLater(new Runnable()
{
public void run()
{
label.setText(value);
}
});
}
}}).start();
}
public void propertyChange(final PropertyChangeEvent ev)
{
SwingUtilities.invokeLater(new Runnable()
{
public void run()
{
label.setText((String)ev.getNewValue());
}
});
}
public String getValue()
{
String value;
// extensive calculation
return value;
}
public void setValue(String value)
{
this.value = value;
firePropertyChange(value);
}
Now what happens if the bean informs the GUI about a value-change triggered by another thread? In propertyChange()
we pass a runnable to the EDT via SwingUtiltites.invokeLater()
that can safely alter the label.
Let's take a look at the colors once again: In BeanImpl.java
there are only red lines - so we achieved a non freezing GUI. GUI.java
has almost all lines in green. Since we restrict changes to Swing components to these green lines we are honouring the Swing threading rule too.
But the red lines in GUI.java
make things difficult: The programmer of this class always has to know which thread is stepping through what part of the code - without any visual help of thread-coloring. Any mistake reintroduces the problems mentioned above.
SwingWorker is a utility class that aims to ease the efforts to write a non-freezing GUI. Although not included in the standard Java distribution it is maintained by the Swing team and downloadable at The Swing Connection.
As you can see in the following example a SwingWorker removes some of the visual clutter seen in the previous section. To use it you have to subclass it, placing extensive calculations into method construct()
. In finished()
you can alter the label because this method is called on the EDT. This is similar to our previous solution but this time the threading is handled by the superclass:
public void actionPerformed(ActionEvent e)
{
label.setText("...");
new SwingWorker()
{
public Object construct()
{
return bean.getValue();
}
public void finished()
{
label.setText((String)getValue());
}
}).start();
}
public void propertyChange(final PropertyChangeEvent ev)
{
SwingUtilities.invokeLater(new Runnable()
{
public void run()
{
label.setText((String)ev.getNewValue());
}
});
}
public String getValue()
{
String value;
// extensive calculation
return value;
}
public void setValue(String value)
{
this.value = value;
firePropertyChange(value);
}
The SwingWorker offers no support for our notification problem so we stick to our previous solution in propertyChange()
.
What about the colors?
The situation hasn't really improved. The indentation of code was minimized but we still have red lines in GUI.java
. So the problem above isn't resolved yet, we're still seeking a better solution.