 |
The Single Thread Rule in Swing
Posted by cayhorstmann on June 11, 2007 at 09:44 PM | Comments (20)
The Single Thread Rule in Swing
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:
- What does it mean that this is the “preferred” way? What
other non-bad ways are there? Or is the bad approach not so bad after
all?
- How can I come up with a tangible example of the badness of the bad
approach? Rumor
has it that ComponentEventDemo
would deadlock on Solaris if the GUI was created in the main thread, but
that program has other threading problems, as evidenced by this
comment:
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.
- The Javadoc says that you should call SwingUtilities.invokeLater.
According to that method's Javadoc, “As of 1.3 this method is just
a cover for java.awt.EventQueue.invokeLater().” So, why
not recommend to use EventQueue.invokeLater? It seems clearer,
and it is four letters shorter.
- Should I really go through the trouble of uglifying correcting
dozens of Swing examples in Core Java? Is it worth worrying about it?
After all, there must be thousands of programs out there that rely on
the old version of the single thread rule.
Bookmark blog post: del.icio.us Digg DZone Furl Reddit
Comments
Comments are listed in date ascending order (oldest first) | Post Comment
-
- You *could* use EQ.invokeAndWait(), or even sillier approaches such as posting a custom component event with a handler that runs the code (basically what EQ.invokeLater() does). EventQueue.invokeLater() is the preferred way because it is the simplest and most used/tested.
- It's hard to create examples that demonstrate concurrency problems consistently across platforms, so I don't recommend you invest the time trying to do that. The EventDispatchThread is created when the first event is posted to the EventQueue, and that used to happen when the first frame was made visible. However, this assumption is no longer true for some toolkits (events are fired sooner), so the only safe course an application can take is to assume that the EQ is always active. That's why the rule changed.
- The Javadoc is out-of-date -- java.awt.EventQueue methods are preferred over their Swing wrappers (the methods used to be in Swing, and are there just for backwards-compatibility). It's a very minor issue, though, as the cost of a single method call is very small these days.
- Wait for the Swing Application Framework (JSR-296) to update your examples? It looks like it will really make Swing application development simpler, and eliminate lots of headaches like this one.
Posted by: tball on June 11, 2007 at 10:43 PM
-
Alexander Potochkin has a nice example of the dangers of not using invokeLater in main.
Swing text is the real villain. There is an attempt to make it thread-safe, but in order to interact with the rest of Swing it needs to use invokeLater. Unfortunately the solution actually takes Swing from being thread-agnostic to thread-hostile.
Consider document events. The event objects have document offsets associated with them. These event are (or may be, the rules seem to have been made up as they went along) passed through invokeLater. Of course the document may have been updated between event fire and listener execution. This is even true with equivalent invokeLaters fired from the EDT. Even were the events objects implemented with Position, a lock still needs to be held on the document to do anything useful. AbstractDocument's peculiar locking arrangement requires code requiring to lock the document to subclass it. So you need your listeners to subclass your document. That is an eccentric arrangement.
SwingUtilities vs EventQueue: SwingUtilities is a dumping ground. EventQueue is where the action is at. I'm not sure why so much example code using SwingUtilities (other than because example code usually seems to show what not to do).
I think you should uglify the code. Perhaps JDK1.8 will make it terser. It's unfortunate that small example require the same boilerplate code as large application. Such is life. If I'm looking for a problem in code (which I normally am), then I want all the obvious things closed off before I look further.
Posted by: tackline on June 12, 2007 at 12:06 AM
-
I recommend using static imports and conveniently delaying the revision of the book until closures are in the language, then you can just do:
invokeLater()
{
JFrame frame=...
}
When APIs/languages make good code ugly, they promote bad code.
Posted by: ricky_clarkson on June 12, 2007 at 02:54 AM
-
1. Pedantically you should use the new "preferred" way. Realistically, doing it the old way will always work. (the API developers are obsessive about backward compatibility).
2. A working example of the old way that fails, would be considered a "bug" by the API developers.
3. Use what produces clearer code for the reader.
4. Well, ... I hope all your code is the "gold standard" of correct, well written, easily understood Java programs. A well written program has a natural beauty to it. If you think the new way makes your code ugly, then you need to ponder a way to make the ugly more appealing.
P.S. Thank you for your chapter on RMI, it is by far the best explanation of RMI I have found.
Posted by: joesephjava on June 12, 2007 at 06:44 AM
-
tackline: I looked through Alexander Potochkin's blog. His example in http://weblogs.java.net/blog/alexfromsun/archive/2005/11/index.html makes calls to setSelectionStart/End after calling setVisible. I'd still love an example where something bad happens to a program that respected the old single thread rule. I suppose what I would need is an API call that starts the EDT. tball: Any specific suggestion? I do think it's worth doing--seeing is believing.
Posted by: cayhorstmann on June 12, 2007 at 07:25 AM
-
I had another look at Alexander Potochkin's example, and I am now ready to believe that "Swing text is the real villain". I traced through the call to setSelectionEnd and got to this code in BasicTextUI:
void changeCaretPosition(int dot, Position.Bias dotBias) {
// repaint the old position and set the new value of
// the dot.
repaint();
// Make sure the caret is visible if this window has the focus.
if (flasher != null && flasher.isRunning()) {
visible = true;
flasher.restart();
}
// notify listeners at the caret moved
this.dot = dot;
this.dotBias = dotBias;
dotLTR = isPositionLTR(dot, dotBias);
fireStateChanged();
updateSystemSelection();
setMagicCaretPosition(null);
// We try to repaint the caret later, since things
// may be unstable at the time this is called
// (i.e. we don't want to depend upon notification
// order or the fact that this might happen on
// an unsafe thread).
Runnable callRepaintNewCaret = new Runnable() {
public void run() {
repaintNewCaret();
}
};
SwingUtilities.invokeLater(callRepaintNewCaret);
}
Sure enough, when I made this call from the main thread, before calling setVisible, the EDT thread was started and ran briefly.
Posted by: cayhorstmann on June 12, 2007 at 08:56 AM
-
Having built many fixtures testing a variety of Swing components on and off the Swing thread, it's the text components that have caused nearly *all* problems, and usually due to setting the text off the EDT before the text components are realized. The abbot UI test framework actually depends on being able to read component properties off the EDT; tests would be unreadable and unnecessarily complex otherwise, or there would be an explosion of test API to wrap all the ugliness.
I considered modifying my fixtures to always launch code under test on the EDT (in order to follow the invokeLater in main examples), but that resulted in a) ugly code b) code running differently than it might assume under normal circumstances and c) no tangible benefit.
I think in your case there's no tangible benefit to changing simple examples to be more complicated. Explaining the circumstances should be sufficient.
Posted by: twalljava on June 12, 2007 at 09:44 AM
-
This is not a recent change. This happened three years ago in 2004. Sun changed the Swing tutorial to do all startup via invokeLater() at that time. May I suggest that you pay some closer attention to a subject you write a book about?
The reason for the change? Well, there was an official one in the tutorial which went along the line of "because it is better". The real reason? Sun couldn't be arsed to clean up several race and deadlock condition in Swing. It was the time when Sun didn't care about the desktop at all, as opposite to the lip-service they are paying to desktop technology today.
re 1. invokeAndWait() is an obvious non-bad candidate for ensuring something gets done on the EDT. Toolkit.getSystemEventQueue().postEvent() is another non-bad way. If you look at them (the API documentation is your friend), you should see why invokeLater() is designated "preferred". The others don't gain you anything in addition during startup.
re 2. You can't.
re 3. SwingUtilities.invokeLater() was the better choice, because it was not always a cover for EventQueue.invokeLater(). It once did some nasty tricks before scheduling the Runnable. For backward compatibility, in case you application or source code should still be good for a 1.2 VM (urgs), SwingUtilities is the only choice. Also, you don't have to refer to an AWT package for this issues (although there are many other reasons why one needs to refer to AWT when doing Swing that this hardly matters). It is maybe more a matter of style. If you program for Swing, and there is a Swing way to do it, use the Swing, not the AWT way. BTW, the remark about less characters is laughable.
re 4. Yes. You have a responsibility to teach your readers the right way. Your personal taste ("ugly"), doesn't matter. Part of this responsibility is to establish the right way as a reoccurring pattern, even in extremely simple examples. You can't teach an old dog new tricks, so please teach the young dogs the right tricks. There are already enough bad Java books out there, written by clueless authors. Don't join their ranks by doing a feel-good book-update instead of an accurate one.
Posted by: ewin on June 12, 2007 at 12:38 PM
-
- I too struggle to come up with a way to do it right but also win the hearts and minds of my students. No luck so far. I differ with ewin, in that I believe ugly does matter. Of course, correctness also matters.
- I just blogged again on this topic and came up with some code that's even uglier, but could have a smoother transition to the JSR-296 way of doing things. This may be worth considering if the Swing Application Framework catches on and we'll be changing our code examples for the (n+1)th editions of our books. [aside: Core Java is up to edition 8? That's amazing.]
- I too would like to see a simple "tangible example of the badness of the bad approach." If the people who are saying none exists are correct, then it's hard to justify such a sweeping change of the Swing threading rule, no?
- EventQueue.invokeLater() is four characters shorter than SwingUtilities.invokeLater(), but SwingUtilities doesn't require another import statement if javax.swing.* has already been included. That's the only reason I chose SwingUtilities for my ugly example. The extra method call doesn't bother me, but there's something to be said for standardizing on something. (If JSR-296 prevails, we will standardize on neither.)
- The posted changeCaretPosition() code is from DefaultCaret, not TextUI, right? It seems that it could be changed to only call invokeLater() if it wouldn't cause problems. [DefaultCaret is weird anyway in that damage() is usually, but not quite always, called before paint(). What's the deal with that?]
- Back to ewan: "This is not a recent change. This happened three years ago in 2004. .... May I suggest that you pay some closer attention to a subject you write a book about?" --
Well sure it's good to pay close attention, but Cay and I aren't the only the only Swing professionals who missed this. I've met many. Evidently something semi-official happened at a JavaOne (in 2004, you say?) but what percentage of Swing programmers had the time and money to attend? How were the rest of us supposed to find out? I more-or-less wrote the blog entry to which Cay linked trying to find an official decree and found only crumbs.
Posted by: bitguru on June 12, 2007 at 03:18 PM
-
Hello Cay
The Swing single-thread rule topic keep appearing here and there
Here my answers to your questions:
1. The first example with "new Runnable()" is the preferred way
the second version is good too, but I personally don't like it since it adds
one needless method - run() to the PureGoodness
public API
There are not so many non-bad approaches left:
- invokeAndWait
- new Swing ApplicationFramework
- SwingWorker
Swing ApplicationFramework is the best choice among them
2. As I already mentioned - "If you work with Swing not from EDT all sorts of strange things can happen" and deadlock is one of them
3. It is a matter of style -
Swing developers use SwingUtilities.* methods
AWT developers prefer EventQueue.* methods
What is your choice ? ;-)
4.If you want to write a correct book you must correct your examples with invokeLater or use SwingAppFramework or create your own little helper class which will do all invokeLater stuff behind the scene
You might be also interested in this blog where I summarize the possible ways of detecting the violations of the "Single thread rule"
Thanks
alexp
Posted by: alexfromsun on June 13, 2007 at 02:26 AM
-
I just noticed that my blogs were already mentioned here
Thanks tackline
tball, ewin and bitguru - your answers are perfect !
It was a pleasure to read them
Thanks everyone
alexp
Posted by: alexfromsun on June 13, 2007 at 02:36 AM
-
Maybe it already happened in 2003. I too used to tell people in my FAQ that the change happened in 2004. But now I did a little bit more digging.
On http://java.sun.com/docs/books/tutorial/information/download.html is a link to an old Swing tutorial version. That link has a comment "(last updated May 19, 2003)". After downloading and unpacking I found the following in the file uiswing/misc/threads.html
Note: We used to say that you could create the GUI on the main
thread as long as you didn't modify components that
had already been realized.
[PENDING: The following red text belongs in a footnote.]
Realized means
that the component has been painted onscreen, or is ready to
be painted. The methods setVisible(true) and
pack cause a window to be realized, which in
turn causes the components it contains to be realized.
While this worked for most applications, in
certain situations it could cause problems.
Out of all the demos in the Swing Tutorial, we encountered
a problem only in ComponentEventDemo. In that case,
sometimes when you launched the demo, it would not come up
because it would deadlock when updating the text area if
the text area had not yet been realized,
while other times it would come up without incident.
To avoid the possibility of thread problems,
we recommend that you use invokeLater to
create the GUI on the event-dispatching thread for all
new applications.
If you have old programs that are working fine
they are probably OK; however you might want to convert
them when it's convenient to do so.
So something was fishy in Swing land in 2003 or 2004. It couldn't be fixed, so the rules were changed. But the change of rules was not properly communicated by Sun. At least I don't regard a remark in a tutorial, or the silent change of a JavaDoc as proper communication. This is actually a nice example why many Java GUI developers feel left-down by Sun. You have to be lucky to find the important information. This lack of communication can't be fixed with software, so I don't have high hopes for the Swing framework.
Posted by: twe on June 13, 2007 at 03:00 AM
-
It's not prettier, or easier in other languages
http://www.ditchnet.org/wp/2007/02/06/cocoa-vs-swing-vs-net-invoking-a-method-on-the-main-gui-thread/
I think letting them experience a deadlock, and how to getaround is a good example for them in the realm of multi threading.
This is just my experience, but I've seen so many Java enterprise guys come through my door and not understand multi threading. However, swing guys + gui developers definitely know.
perhaps multi threading is too advanced a topic for what you are teaching now...but it's so critical IMHO.
Posted by: javaguy44 on June 13, 2007 at 05:01 AM
-
Would it be possible to create an "event queue" annotation for methods that would rewrite the method to check if the current thread is the swing thread and if not, create an event to be posted later? Here is an example of what I am thinking:
@OnSwingThread
public void swingOp()
{
// swing code here
}
This would be rewritten with the compiler to something like this:
public void swingOp()
{
if (onSwingThread()) {
// swing code here
}
else {
EventQueue.invokeLater(new Runnable() {
public void run() {
swingOp();
}
});
}
}
So the final code resembles the .NET pattern, but the original with the annotation is not ugly. What do you guys think?
Posted by: droidix on June 14, 2007 at 05:25 AM
-
Here is my opinion : there's many "geeks" comments on this post. The real question for your book is : what do you want to explain? swing or threads. There's one basic concept in object programming : single responsability principle (SRP). I think you could use it writing your book. Don't propose too complex code. Concentrate examples on the main subject. By the way, it could be great to dedicate a chapter to threads with swing.
Posted by: alexdp on June 21, 2007 at 06:14 AM
-
Quality and usefulness of code examples alone is what in my opinion makes Core Java great. The examples should reflect the latest coding practices too. Since examples from Core Java tend to get into the minds of some people and subsequently into production code.
Posted by: avrecko on June 25, 2007 at 03:33 AM
-
Well, I've always invoked setVisible after setting the size of JFrame etc etc....but I have never done this specifically within the event queue. My understanding has always been that JFrame is realized after setVisible(true) is executed.
Since I've been doing it this way for a decade or so and since I am pretty sure I read from a reliable(Sun) source that this is how it's to be done, I am now very interested in seeing an example where this results in something bad happening.
Posted by: swv on June 28, 2007 at 07:18 PM
-
swv: It isn't easy to come up with an example that reliably screws up. The best line of attack would be to do a whole lot of text processing in the frame constructor to trigger the morass in javax.swing.text. But the result would be completely artificial--nobody would do a long loop of text editing commands in a real app. I would not lose sleep over existing Swing apps, but I did switch to EventQueue.invokeLater in the Core Java examples. BTW, I think these issues could have been fixed, but it was just so much easier to change the rules.
Posted by: cayhorstmann on June 28, 2007 at 07:43 PM
-
It sounds like a lot more people need to read this book:
http://www.amazon.com/Java-Concurrency-Practice-Brian-Goetz/dp/0321349601/ref=pd_bbs_sr_1/102-8208995-6673746?ie=UTF8&s=books&qid=1183118923&sr=8-1
Sun should have put a lot more of emphasis on the right way to use threads, far earlier, then large books like this would be less needed to undo the damage and shame Suns' inadequate thread safeness information and locking criteria in the autodocs.
From a reading (in progress) this book, I saw multiple gaffs in my Swing components, so fixed them and now my client programs run faster and smoother, despite more object creation and method calls. BTW I used invokeAndWait, I didn't want invokeLater it seemed like an invitation for race conditions, for my use.
Posted by: infernoz on June 29, 2007 at 05:32 AM
|