Is It Safe to Construct Swing/Awt Widgets Not on the Event Dispatch Thread

Is it safe to construct Swing/AWT widgets NOT on the Event Dispatch Thread?

Sun has changed the rules in 2004 -- before, you were allowed to create the components outside the EDT and only had to move into the EDT once the component had been realized.

The new rule now reads:

To avoid the possibility of deadlock,
you must take extreme care that Swing
components and models are created,
modified, and queried only from the
event-dispatching thread.

this blog post of mine gives more details, including links to other related articles. note that all official Sun examples have been rewritten and are very strict about this.

historically, it probably was the increasing availability of multi-core computers as desktop machines that motivated the re-formulation of the rule -- threading issues became more and more apparent on the client stack, and by being very strict on EDT guidelines, a lot of them can be prevented from the start.

Why Swing components should be accessed on the Event Dispatch Thread only?

It's because the Java memory model does not guarantee that memory writes by one thread will be visible to other threads unless you use some form of synchronization. For performance and simplicity, Swing is not synchronized. Therefore, writes from other threads may never be visible to the EDT.

The application you've seen may work most of the time, and it may even work all of the time in some environments. But when it doesn't work, it'll fail in really strange ways that are difficult to reproduce.

Java: Swing Libraries & Thread Safety

  1. Never do long running tasks in response to a button, event, etc as these are on the event thread. If you block the event thread, the ENTIRE GUI will be completely unresponsive resulting in REALLY pissed off users. This is why Swing seems slow and crusty.

  2. Use Threads, Executors, and SwingWorker to run tasks NOT ON THE EDT ( event dispatch thread).

  3. Do not update or create widgets outside of the EDT. Just about the only call you can do outside of the EDT is Component.repaint(). Use SwingUtilitis.invokeLater to ensure certain code executes on the EDT.

  4. Use EDT Debug Techniques and a smart look and feel (like Substance, which checks for EDT violation)

If you follow these rules, Swing can make some very attractive and RESPONSIVE GUIs

An example of some REALLY awesome Swing UI work: Palantir Technologies. Note: I DO NOT work for them, just an example of awesome swing. Shame no public demo... Their blog is good too, sparse, but good

Is it safe to call fireTableRowsUpdated() within a non-event dispatch thread?

From the documentation for JTable:

Warning: Swing is not thread safe. For more information see Swing's Threading Policy.

This links to the following:

In general Swing is not thread safe. All Swing components and related classes, unless otherwise documented, must be accessed on the event dispatching thread.

And more to the point of your question:

This restriction also applies to models attached to Swing components. For example, if a TableModel is attached to a JTable, the TableModel should only be modified on the event dispatching thread.

Now, it just so happens that you can get away with doing a lot of things that violate this policy. But why do that when it's easy enough to create a SwingWorker and guarantee that your code is thread-safe?

Handling the Event Dispatch Thread

That is generally sufficient until you start making use of background threads for calculations, data acquisition, etc. Then you need to start being careful to verify that you are on the EDT prior to altering a swing component or its model.

You can test whether you're executing on the EDT with:

    if (SwingUtilities.isEventDispatchThread()) {
// Yes, manipulate swing components
} else {
// No, use invokeLater() to schedule work on the EDT
}

Also, see the SwingWorker class for details on how to hand off work to a background thread and process results on the EDT

must all methods in AWT classes, i.e. non-Swing ones, be called in the EDT?

Correct synchronization in a multi-threaded Java program hinges on the happens-before relation, summarized in Memory Consistency Properties. AWT Components were intended to be thread safe, synchronizing on a private lock object in java.awt.Component. See the comments for some historical perspective:

private transient Object objectLock = new Object();

While this may prove sufficient for simple programs, more complex programs are required to rely on knowledge of this implementation detail to verify correct synchronization. It's possible, but who wants to settle for a brittle AWT GUI?

Some additional points:

  • The article cited by @Hovercraft dates to 1998, but it has been updated repeatedly to address such issues as the new memory model mentioned in the usenet thread you cited.

  • The evolution of javax.swing has been away from GUI API promises, as mentioned here, and toward more flexible concurrent programming tools.

Creating an Event Dispatch Thread safe semaphore

Using a Semaphore is most likely not the correct approach. What you want is to enter nested event loops, not use blocking mechanisms. From reading the API it also appears you are over-complicating things. Again, all you need is to enter a nested event loop on one UI thread and then exit that loop once the other UI thread has completed its work. I believe the following meets your requirements:

import java.awt.EventQueue;
import java.awt.SecondaryLoop;
import java.awt.Toolkit;
import java.util.Objects;
import java.util.concurrent.atomic.AtomicReference;
import java.util.function.Supplier;
import javafx.application.Platform;
import javax.swing.SwingUtilities;

public class Foo {

public static <T> T getOnFxAndWaitOnEdt(Supplier<? extends T> supplier) {
Objects.requireNonNull(supplier, "supplier");
if (!EventQueue.isDispatchThread()) {
throw new IllegalStateException("current thread != EDT");
}

final SecondaryLoop loop = Toolkit.getDefaultToolkit()
.getSystemEventQueue()
.createSecondaryLoop();
final AtomicReference<T> valueRef = new AtomicReference<>();

Platform.runLater(() -> {
valueRef.set(supplier.get());
SwingUtilities.invokeLater(loop::exit);
});
loop.enter();

return valueRef.get();
}

public static <T> T getOnEdtAndWaitOnFx(Supplier<? extends T> supplier) {
Objects.requireNonNull(supplier, "supplier");
if (!Platform.isFxApplicationThread()) {
throw new IllegalStateException(
"current thread != JavaFX Application Thread");
}

final Object key = new Object();
final AtomicReference<T> valueRef = new AtomicReference<>();

SwingUtilities.invokeLater(() -> {
valueRef.set(supplier.get());
Platform.runLater(() -> Platform.exitNestedEventLoop(key, null));
});
Platform.enterNestedEventLoop(key);

return valueRef.get();
}

}

The Platform#enterNestedEventLoop and Platform#exitNestedEventLoop methods were added in JavaFX 9 though there are equivalent internal methods in JavaFX 8. The reason AtomicReference is used is because local variables must be final or effectively final when used inside a lambda expression. However, due to the way the separate threads are notified I don't believe the volatility semantics provided by the #get() and #set(T) methods of AtomicReference is strictly needed but I've used those methods just in case.

Here's an example of using the above to show a modal JavaFX dialog from the Event Dispatch Thread:

Optional<T> optional = Foo.getOnFxAndWaitOnEdt(() -> {
Dialog<T> dialog = new Dialog<>();
// configure dialog...
return dialog.showAndWait();
});

The above utility methods are for communicating from the Event Dispatch Thread to the JavaFX Application Thread and vice versa. This is why entering a nested event loop is necessary, otherwise one of the UI threads would have to block and that would freeze the associated UI. If you're on a non-UI thread and need to run an action on a UI thread while waiting for the result the solution is much simpler:

// Run on EDT
T result = CompletableFuture.supplyAysnc(/*Supplier*/, SwingUtilities::invokeLater).join();

// Run on FX thread
T result = CompletableFuture.supplyAsync(/*Supplier*/, Platform::runLater).join();

The call to join() will block the calling thread so be sure not to call the method from either of the UI threads.



Related Topics



Leave a reply



Submit