views:

158

answers:

5

In the past few years I've mostly done UI development in Eclipse, which is very conservative in terms of thread access: any attempt to change a property on a UI widget (e.g., color, text) from outside the UI thread throws an exception.

I am now looking at an existing program in Swing that has a window with a large number of custom widget. There is a separate threads that runs a mutation function for each of these widgets, and the mutation function reads the value of some things (e.g., label colors and values) and writes some (e.g., changes background colors). Note that there is no custom painting involved or anything like that, just a bunch of changes to the sub widgets it contains which are mostly JLabels.

At present, this runs from the separate thread, not from the Swing event thread. This thread goes over all the 400 widgets and calls the mutator on each. The updates seem to work correctly, but the GUI is unresponsive to user input.

If I take the whole thing, which runs for about 0.4 msec from outside the Swing thread and wrap every call to a mutator in an invokeLater or invokeAndWait, the UI is a lot more responsive.

What I'm trying to understand is:

1) Is it sometimes legitimate to make all these calls from outside the Swing thread?

2) What is the impact on the Swing thread and why is the UI less responsive when I call it from outside?

+1  A: 

The basic problem is that non-thread-safe objects are executed in a multithreaded fashion. Even something as simple as reading a HashMap can be caught in an infinite loop. Because AWT uses locks (badly), you can also end up with deadlocks. However, you are likely to get away with it, although you might find that an updated version of Java suddenly causes you issues on some customer machines.

(BTW: It's AWT's Event Dispatch Thread, not Swing's.)

Tom Hawtin - tackline
+1  A: 

For (1) as a rule of thumb anything that updates pixels on screen must be called from the Event Dispatching Thread (EDT). Where some JVMs may handle updates from outside the EDT acceptably you should never rely on this working, some machines and different look and feels will not work acceptably. The behaviour is undefined - and this could explain the lack of responsiveness you have seen.

Pool
+3  A: 

From "Nothing" to intermittent problems to "Everything Broke, pull everyone off to work on the GUI!"

The main (most obvious) visual effect is that if you hold up the GUI thread (like someone presses a button and you do a sleep(5000) or something), your GUI won't repaint. It can't because you are holding onto the only thread it's allowed to pass you! This makes people think Java is really slow. It's not bad, but it's easy enough to program that a lot of people who don't bother researching practices like this have produced shipping products.

The next biggest problem is that when you are drawing the screen in another thread (like the one passed to main), it can have strange behavior. Swing is already too picky about how you render your frames--take out threading as a variable!

Finally, rarely (or often if you are calling a swing component in a tight loop on the wrong thread) you can get thread collisions. If that happens an exception may be thrown (or not) and something will probably render wrong, but it may not be obvious.

Bill K
+3  A: 

1) Is it sometimes legitimate to make all these calls from outside the Swing thread?

There are a few exceptions (setting the text value of a text field, for example, does automatic EDT proxying for you) - but there are no situations where it is better to do so. If you are performing lots of updates, you can do them all in a single EDT call (a single call to invokeLater()) instead of individual calls - but even that sort of batching very rarely helps things. Long and short: Perform operations on Swing components from the EDT. That includes reads and writes.

2) What is the impact on the Swing thread and why is the UI less responsive when I call it from outside?

Well, the EDT is responsible for updating the GUI. If you call from outside, it isn't 'less responsive' - it's that the actual low level system calls that update the user interface don't occur (at all). What is probably happening in your app is that the original developers are getting lucky and changing state in the swing component without creating a really nasty race condition. Then some other event is causing a repaint to occur on the EDT, which results in the component being updated. This may appear to be a 'lack of responsiveness' - but what's really happening is a 'lack of screen refresh'.

The EDT is just a regular thread, but it is a bit special in that it runs in a tight loop that processes GUI related signals (draw commands, for example). The semantecs of posting these types of commands on the EDT is really, really different from what we typically think of as Java threading (it involves submitting operations to a message pump).

Long and short - all those Javadocs that say 'only interact with Swing objects on the EDT' are written for a reason. Don't mess with it. If you want to do background processing, fine - but you are responsible for proxying the interaction with the J* components back onto the EDT (most generally using invokeLater() ).

Kevin Day
+2  A: 
  1. There really are no exceptions. Kevin is partially correct - the JTextComponent.setText() is advertised as thread safe. However, looking at the 1.6 code, it provides synchronization on the document object and does not use the EDT. This is fine, unless another swing component (or something that controls swing components) is listening to the document object. Save yourself the trouble of worrying about it, and just always use the EDT - like Kevin says, there really are no situations (that I'm aware of) to do otherwise.

  2. Hard to say without digging into the code; the behavior is undefined. If your background tasks were long running (> a few seconds), you'd see the opposite effect - using the EDT will make the UI unresponsive while your tasks are running.

Fortunately, it sounds like doing it the right way is working best for you anyway. :)

Sun used to say it was ok to use other threads with components that haven't been realized, but later recanted:

Related stackoverflow question

Check out Sun's UI tutorial on swing and concurrency (I'd post the link, but this is my first answer on stackoverflow0.

Scott A Miller