I am working on the 8th edition of Core Java, and one of my thoughtful reviewers (Brian Goetz) pointed out that the single threading rule for Swing has been tightened up recently. This blog discusses how code that used to be good is now bad, and it tries to elicit some answers from the experts about that badness.
I am working on the 8th edition
of Core Java, and I just
received a batch of very thoughtful comments from Brian Goetz about the concurrency
chapter. (Thanks Brian!!!) He points out that I missed an important change
in the Swing single thread rule. (Judging by this
blog, I am not the only author who did.)
The original rule is well explained in this classic article. It is:
Once a Swing component has been realized, all code that might affect or depend on the state of that component should be executed in the event-dispatching thread.
“Realized” is defined in that article, and it is a bit intricate. But for the most part, it means that you should stop messing with Swing components in the main thread after calling pack (!) or setVisible(true), whichever comes first.
For example, the following code would be
bad.
public class BadExample { public static void main(String... args) { JFrame frame = new JFrame(); frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); frame.setVisible(true); frame.setSize(300, 300); // BAD } }
But the latest Javadoc goes quite a bit further. It says “All Swing components and related classes, unless otherwise documented, must be accessed on the event dispatching thread.”
In other words, this example is
also bad:
public class StillBad { public static void main(String... args) { JFrame frame = new JFrame(); // BAD frame.setSize(300, 300); // BAD frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); // BAD frame.setVisible(true); // BAD } }
The Javadoc then goes on: “The preferred way to transfer control and begin working with Swing is to use invokeLater.”
Here is the example, rewritten
the good way.
public class PureGoodness { public static void main(String... args) { EventQueue.invokeLater(new Runnable() { public void run() { JFrame frame = new JFrame(); frame.setSize(300, 300); frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); frame.setVisible(true); } }); } }
Ugh. That won't win the hearts and minds of my students. I suppose I could rewrite it as
public class PureGoodness implements Runnable { public void run() { JFrame frame = new JFrame(); frame.setSize(300, 300); frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); frame.setVisible(true); } public static void main(String... args) { EventQueue.invokeLater(new PureGoodness()); } }
Still ugh.
I am left with some questions and I fervently hope that some of you have answers:
protected void displayMessage(String message) { //If the text area is not yet realized, and //we tell it to draw text, it could cause //a text/AWT tree deadlock. Our solution is //to ensure that the text area is realized //before attempting to draw text. if (display.isShowing()) { display.append(message + newline); display.setCaretPosition(display.getDocument().getLength()); }I tried creating a bad situation, but I was stymied by the fact that on Linux (and, I believe, on Windows) the event dispatch thread is never created until the call to setVisible. Core Java has a convincing example that shows a combo box breaking when another thread adds and removes items after the event dispatch thread has started. I would very much like something similar that convinces readers to use invokeLater when constructing the UI.