Modern hardware systems have a multi-core architecture. So in contemporary software development concurrency is an even more crucial ingredient than before. But as we will see it is of great importance for single core systems, too. If you have already created a Java Swing application you’ve propably made an acquaintance with the SwingWorker
, in order to delegate long running tasks from within Swing events to another thread. But first things first. In Swing the whole painting and event handling of the graphical user interface is executed in one thread the so called event dispatching-thread from AWT the underlying former window toolkit from Java. Therefore most of Swing-based methods are not thread-safe, meaning that you have to prevent race conditions by yourself, but it offers the possibility to dispatch tasks to the event-dispatcher thread. It is highly recommended to do everything, that updates the GUI in the corresponding thread.
This works as follows. The event dispatcher maintains a queue in which all UI-Events and external runnables, submitted via SwingUtilities#invokeLater()
or SwingUtilities#invokeAndWait()
, are enqueued and executes them one by one:
If you have a long-running task, like searching a file on the hard drive, being invoked by an event, e.g., a button click, you should not do this in the event-handling code, since you would block the whole queue until the task is finished leading to an unresponsive GUI experience. This is also true for single-core machines. Lets take a look at this. Imagine you have a machine with one core and you are executing a task like mentioned before. If you are doing this in the queue, you won’t be able to cancel the task or to do anything on the GUI. It will freeze. But good GUIs don’t freeze, even on single-core machines. Why is that? If you dispatch this task to another thread and w.l.o.g it iterates through a big loop, then you may call Thread#yield()
in every iteration, in order to give the Java VM a hint that it can suspend this thread and give some calculation capacity to the GUI thread. In addition if it waits for example for an I/O-operation, the GUI thread can process some GUI events. Since Java is preemptive (on most machine/operating system combinations), it can (even without ‘yield’-hints) interrupt the external thread at any time, in order to give the dispatcher thread some time and vice versa. So this is (on single-core machines) not real concurrency, but it allows both threads to work alternatingly, which appears to be concurrent. This especially applies for multi-core machines. Furthermore the external task can check for Thread#isInterrupted()
or if it waits, e.g., for interruptable I/O operations it can react to cancel-events of the user and terminate the execution prematurely. Here is a code example:
while(!finished) { if(Thread.interrupted()) { cleanup(); return; } doStuff(); Thread.yield(); }
It is of fundamental significance, that you program long-running tasks this way. If you counteract, you provoke tasks that cannot be canceled, leading to annoying system behavior. The tenet reads: The longer the task, the more eager it should be to react to cancellation.
Analogous to dispatching external execution to the swing thread by enqueuing it, long-running tasks should be send to external threads. For this purpose the Swing Worker has been developed and since Java 5 it is an integral part of the JRE and evolved in Java 6. With the SwingWorker
you can execute a task in another thread and relieve the event dispatcher. In the early versions of the Swing Worker it started new threads for every execution and had no support for getting intermediate progress from it conveniently and show it, e.g., in a progress bar. So I implemented a second queue for executing long-running tasks on an external thread for Java 1.4.2. Lateron I refined it by using the ExecutorService
of Java 5. Now in Java 6, the Swing Worker uses an ExecutorService
by itself with a fixed number of worker threads and offers very convenient features for updating the GUI with progress and canceling an execution.
Was it worth implementing my own heavy weight task executor? Yes! Since the SwingWorker starts a fixed number of worker threads, that cannot be configured there might arise race conditions between the data structures and resources accessed in the external tasks. If you are a fan of the one-thread-philosophy of Swing, because it naturally serializes/sequentializes the access to the resources you may use a second queue for these tasks and exchange tasks between the GUI thread and the external thread. In this sense, long-running tasks are sent to the heavy weight task executor and GUI updates — like progress updates — are sent back.
How this can be achieved will be the topic of another post (as usual with a running code example for a rudimentary heavy weight task executor).
One thought to “Task Queue for Heavy Weight Tasks in JavaSE Applications”