Why you need a concurrency framework in JavaFX
How the Worker<V> interface represents a concurrent task
How to run a one-time task
How to run a reusable task
How to run a scheduled task
The Need for a Concurrency Framework
Java (including JavaFX) GUI (graphical user interface) applications are inherently multithreaded. Multiple threads perform different tasks to keep the UI in sync with the user actions. JavaFX, like Swing and AWT, uses a single thread, called the JavaFX Application Thread, to process all UI events. The nodes representing the UI in a scene graph are not thread-safe. Designing nodes that are not thread-safe has advantages and disadvantages. They are faster, as no synchronization is involved. The disadvantage is that they need to be accessed from a single thread to avoid being in an illegal state. JavaFX puts a restriction that a live scene graph must be accessed from one and only one thread, the JavaFX Application Thread. This restriction indirectly imposes another restriction that a UI event should not process a long-running task, as it will make the application unresponsive. The user will get the impression that the application is hung.
A Label to display the progress of a task
A Start button to start the task
An Exit button to exit the application
Performing a Long-Running Task in an Event Handler
The program is very simple. When you click the Start button, a task lasting for ten seconds is started. The logic for the task is in the runTask() method , which simply runs a loop ten times. Inside the loop, the task lets the current thread, which is the JavaFX Application Thread, sleep for one second. The program has two problems.
Click the Start button and immediately try to click the Exit button. Clicking the Exit button has no effect until the task finishes. Once you click the Start button, you cannot do anything else on the window, except to wait for ten seconds for the task to finish. That is, the application becomes unresponsive for ten seconds. This is the reason you named the class UnresponsiveUI .
Inside the loop in the runTask() method , the program prints the status of the task on the standard output and displays the same in the Label in the window. You see the status updated on the standard output, but not in the Label.
It is repeated to emphasize that all UI event handlers in JavaFX run on a single thread, which is the JavaFX Application Thread. When the Start button is clicked, the runTask() method is executed in the JavaFX Application Thread. When the Exit button is clicked while the task is running, an ActionEvent event for the Exit button is generated and queued on the JavaFX Application Thread. The ActionEvent handler for the Exit button is run on the same thread after the thread is done running the runTask() method as part of the ActionEvent handler for the Start button.
A pulse event is generated when the scene graph is updated. The pulse event handler is also run on the JavaFX Application Thread. Inside the loop, the text property of the Label was updated ten times, which generated the pulse events. However, the scene graph was not refreshed to show the latest text for the Label, as the JavaFX Application Thread was busy running the task and it did not run the pulse event handlers.
Both problems arise because there is only one thread to process all UI event handlers, and you ran a long-running task in the ActionEvent handler for the Start button.
What is the solution? You have only one option. You cannot change the single-threaded model for handling the UI events. You must not run long-running tasks in the event handlers. Sometimes, it is a business need to process a big job as part of a user action. The solution is to run the long-running tasks in one or more background threads, instead of in the JavaFX Application Thread.
A Program Accessing a Live Scene Graph from a Non-JavaFX Application Thread
The JavaFX runtime checks that a live scene must be accessed from the JavaFX Application Thread. The runTask() method is run on a new thread, named Thread-4 as shown in the stack trace, which is not the JavaFX Application Thread. The foregoing statement sets the text property for the Label, which is part of a live scene graph, from the thread other than the JavaFX Application Thread, which is not permissible.
public static boolean isFxApplicationThread()
public static void runLater(Runnable runnable)
The isFxApplicationThread() method returns true if the thread calling this method is the JavaFX Application Thread. Otherwise, it returns false.
The runLater() method schedules the specified Runnable to be run on the JavaFX Application Thread at some unspecified time in future.
If you have experience working with Swing, the Platform.runLater() in JavaFX is the counterpart of the SwingUtilities.invokeLater() in Swing.
A Responsive UI That Runs Long-Running Tasks in a Background Thread
Now, setting the text property for the Label takes place on the JavaFX Application Thread. The ActionEvent handler of the Start button runs the task in a background thread, thus freeing up the JavaFX Application Thread to handle user actions. The status of the task is updated in the Label regularly. You can click the Exit button while the task is being processed.
Did you overcome the restrictions imposed by the event-dispatching threading model of the JavaFX? The answer is yes and no. You used a trivial example to demonstrate the problem. You have solved the trivial problem. However, in the real world, performing a long-running task in a GUI application is not so trivial. For example, your task-running logic and the UI are tightly coupled as you are referencing the Label inside the runTask() method, which is not desirable in the real world. Your task does not return a result, nor does it have a reliable mechanism to handle errors that may occur. Your task cannot be reliably cancelled, restarted, or scheduled to be run at a future time.
The JavaFX concurrency framework has answers to all these questions. The framework provides a reliable way of running a task in one or multiple background threads and publishing the status and the result of the task in a GUI application. The framework is the topic of discussion in this chapter. I have taken several pages just to make the case for a concurrency framework in JavaFX. If you understand the background of the problem as presented in this section, understanding the framework will be easy.
Understanding the Concurrency Framework API
The framework consists of one interface, four classes, and one enum.
An instance of the Worker interface represents a task that needs to be performed in one or more background threads. The state of the task is observable from the JavaFX Application Thread.
The Task, Service, and ScheduledService classes implement the Worker interface. They represent different types of tasks. They are abstract classes. An instance of the Task class represents a one-shot task. A Task cannot be reused. An instance of the Service class represents a reusable task. The ScheduledService class inherits from the Service class. A ScheduledService is a task that can be scheduled to run repeatedly after a specified interval.
The constants in the Worker.State enum represent different states of a Worker.
An instance of the WorkerStateEvent class represents an event that occurs as the state of a Worker changes. You can add event handlers to all three types of tasks to listen to the change in their states.
Understanding the Worker<V> Interface
The Worker<V> interface provides the specification for any task performed by the JavaFX concurrency framework. A Worker is a task that is performed in one or more background threads. The generic parameter V is the data type of the result of the Worker. Use Void as the generic parameter if the Worker does not produce a result. The state of the task is observable. The state of the task is published on the JavaFX Application Thread, making it possible for the task to communicate with the scene graph, as is commonly required in a GUI application.
State Transitions for a Worker
Worker.State.READY
Worker.State.SCHEDULED
Worker.State.RUNNING
Worker.State.SUCCEEDED
Worker.State.CANCELLED
Worker.State.FAILED
When a Worker is created, it is in the READY state. It transitions to the SCHEDULED state, before it starts executing. When it starts running, it is in the RUNNING state. Upon successful completion, a Worker transitions from the RUNNING state to the SUCCEEDED state. If the Worker throws an exception during its execution, it transitions to the FAILED state. A Worker may be cancelled using the cancel() method. It may transition to the CANCELLED state from the READY, SCHEDULED, and RUNNING states. These are the normal state transitions for a one-shot Worker.
A reusable Worker may transition from the CANCELLED, SUCCEEDED, and FAILED states to the READY state as shown in the figure by dashed lines.
Properties of a Worker
title
message
running
state
progress
workDone
totalWork
value
exception
When you create a Worker, you will have a chance to specify these properties. The properties can also be updated as the task progresses.
The title property represents the title for the task. Suppose a task generates prime numbers. You may give the task a title “Prime Number Generator.”
The message property represents a detailed message during the task processing. Suppose a task generates several prime numbers; you may want to give feedback to the user at a regular interval or at appropriate times with a message such as “Generating X of Y prime numbers.”
The running property tells whether the Worker is running. It is true when the Worker is in the SCHEDULED or RUNNING states. Otherwise, it is false.
The state property specifies the state of the Worker. Its value is one of the constants of the Worker.State enum.
The totalWork, workDone, and progress properties represent the progress of the task. The totalWork is the total amount of work to be done. The workDone is the amount of work that has been done. The progress is the ratio of workDone and totalWork. They are set to –1.0 if their values are not known.
The value property represents the result of the task. Its value is non-null only when the Worker finishes successfully reaching the SUCCEEDED state. Sometimes, a task may not produce a result. In those cases, the generic parameter V would be Void, and the value property will always be null.
A task may fail by throwing an exception. The exception property represents the exception that is thrown during the processing of the task. It is non-null only when the state of the Worker is FAILED. It is of the type Throwable.
Typically, when a task is in progress, you want to display the task details in a scene graph. The concurrency framework makes sure that the properties of a Worker are updated on the JavaFX Application Thread. Therefore, it is fine to bind the properties of the UI elements in a scene graph to these properties. You can also add Invalidation and ChangeListener to these properties and access a live scene graph from inside those listeners.
In subsequent sections, you will discuss specific implementations of the Worker interface. Let us create a reusable GUI to use in all examples. The GUI is based on a Worker to display the current values of its properties.
Utility Classes for Examples
A Utility Class to Build UI Displaying the Properties of a Worker
A Utility Class to Work with Prime Numbers
Using the Task<V> Class
An instance of the Task<V> class represents a one-time task. Once the task is completed, cancelled, or failed, it cannot be restarted. The Task<V> class implements the Worker<V> interface. Therefore, all properties and methods specified by the Worker<V> interface are available in the Task<V> class.
The Task<V> class inherits from the FutureTask<V> class , which is part of the Java concurrency framework. The FutureTask<V> implements the Future<V>, RunnableFuture<V>, and Runnable interfaces. Therefore, a Task<V> also implements all these interfaces.
Creating a Task
Updating Task Properties
protected void updateMessage(String message)
protected void updateProgress(double workDone, double totalWork)
protected void updateProgress(long workDone, long totalWork)
protected void updateTitle(String title)
protected void updateValue(V value)
You provide the values for the workDone and the totalWork properties to the updateProgress() method. The progress property will be set to workDone/totalWork. The method throws a runtime exception if the workDone is greater than the totalWork or both are less than –1.0.
Sometimes, you may want to publish partial results of a task in its value property. The updateValue() method is used for this purpose. The final result of a task is the return value of its call() method.
All updateXxx() methods are executed on the JavaFX Application Thread. Their names indicate the property they update. They are safe to be called from the call() method of the Task. If you want to update the properties of the Task from the call() method directly, you need to wrap the code inside a Platform.runLater() call.
Listening to Task Transition Events
onCancelled
onFailed
onRunning
onScheduled
onSucceeded
Cancelling a Task
public final boolean cancel()
public boolean cancel(boolean mayInterruptIfRunning)
The first version removes the task from the execution queue or stops its execution. The second version lets you specify whether the thread running the task be interrupted. Make sure to handle the InterruptedException inside the call() method. Once you detect this exception, you need to finish the call() method quickly. Otherwise, the call to cancel(true) may not cancel the task reliably. The cancel() method may be called from any thread.
protected void scheduled()
protected void running()
protected void succeeded()
protected void cancelled()
protected void failed()
Their implementations in the Task class are empty. They are meant to be overridden by the subclasses.
Running a Task
A Prime Finder Task Example
It is time to see a Task in action. The program in Listing 24-6 is an implementation of the Task<ObservableList<Long>>. It checks for prime numbers between the specified lowerLimit and upperLimit. It returns all the numbers in the range. Notice that the task thread sleeps for a short time before checking a number for a prime number. This is done to give the user an impression of a long-running task. It is not needed in a real-world application. The call() method handles an InterruptedException and finishes the task if the task was interrupted as part of a cancellation request.
Every time a prime number is found, the results list is updated. The foregoing statement wraps the results list in an unmodifiable observable list and publishes it for the client. This gives the client access to the partial results of the task. This is a quick and dirty way of publishing the partial results. If the call() method returns a primitive value, it is fine to call the updateValue() method repeatedly.
In this case, you are creating a new unmodifiable list every time you find a new prime number, which is not acceptable in a production environment for performance reasons. The efficient way of publishing the partial results would be to declare a read-only property for the Task; update the read-only property regularly on the JavaFX Application Thread; let the client bind to the read-only property to see the partial results.
Finding Prime Numbers Using a Task<Long>
Executing a Task in a GUI Environment
Using the Service<V> Class
The Service<V> class is an implementation of the Worker<V> interface. It encapsulates a Task<V>. It makes the Task<V> reusable by letting it be started, cancelled, reset, and restarted.
Creating the Service
Remember that a Service<V> encapsulates a Task<V>. Therefore, you need a Task<V> to have a Service<V>. The Service<V> class contains an abstract protected createTask() method that returns a Task<V>. To create a service, you need to subclass the Service<V> class and provide an implementation for the createTask() method.
The createTask() method of the service is called whenever the service is started or restarted.
Updating Service Properties
The Service class contains all properties (title, message, state, value, etc.) that represent the internal state of a Worker. It adds an executor property, which is a java.util.concurrent.Executor. The property is used to run the Service. If it is not specified, a daemon thread is created to run the Service.
Unlike the Task class, the Service class does not contain updateXxx() methods for updating its properties. Its properties are bound to the corresponding properties of the underlying Task<V>. When the Task updates its properties, the changes are reflected automatically to the Service and to the client.
Listening to Service Transition Events
The Service class contains all properties for setting the state transition listeners as contained by the Task class. It adds an onReady property. The property specifies a state transition event handler, which is called when the Service transitions to the READY state. Note that the Task class does not contain an onReady property as a Task is in the READY state when it is created, and it never transitions to the READY state again. However, a Service can be in the READY state multiple times. A Service transitions to the READY state when it is created, reset, and restarted. The Service class also contains a protected ready() method, which is intended to be overridden by subclasses. The ready() method is called when the Service transitions to the READY state.
Cancelling the Service
Use the cancel() methods to cancel a Service: the method sets the state of the Service to CANCELLED.
Starting the Service
Resetting the Service
Calling the reset() method of the Service class resets the Service. Resetting puts all the Service properties back to their initial states. The state is set to READY. Resetting a Service is allowed only when the Service is in one of the finish states: SUCCEEDED, FAILED, CANCELLED, or READY. Calling the reset() method throws a runtime exception if the Service is in the SCHEDULED or RUNNING state.
Restarting the Service
cancel()
reset()
start()
The Prime Finder Service Example
Using a Service to Find Prime Numbers
Using the ScheduledService<V> Class
The ScheduledService<V> is a Service<V>, which automatically restarts. It can restart when it finishes successfully or when it fails. Restarting on a failure is configurable. The ScheduledService<V> class inherits from the Service<V> class. The ScheduledService is suitable for tasks that use polling. For example, you may use it to refresh the score of a game or the weather report from the Internet after every ten minutes.
Creating the ScheduledService
The process of creating a ScheduledService is the same as that of creating a Service. You need to subclass the ScheduledService<V> class and provide an implementation for the createTask() method.
The createTask() method of the service is called when the service is started or restarted manually or automatically. Note that a ScheduledService is automatically restarted. You can start and restart it manually by calling the start() and restart() methods.
Starting, cancelling, resetting, and restarting a ScheduledService work the same way as these operations on a Service.
Updating ScheduledService Properties
lastValue
delay
period
restartOnFailure
maximumFailureCount
backoffStrategy
cumulativePeriod
currentFailureCount
maximumCumulativePeriod
A ScheduledService<V> is designed to run several times. The current value computed by the service is not very meaningful. Your class adds a new property lastValue, which is of the type V, and it is the last value computed by the service.
The delay is a Duration, which specifies a delay between when the service is started and when it begins running. The service stays in the SCHEDULED state for the specified delay. The delay is honored only when the service is started manually calling the start() or restart() method. When the service is restarted automatically, honoring the delay property depends on the current state of the service. For example, if the service is running behind its periodic schedule, it will rerun immediately, ignoring the delay property. The default delay is zero.
The period is a Duration, which specifies the minimum amount of time between the last run and the next run. The default period is zero.
The restartOnFailure specifies whether the service restarts automatically when it fails. By default, it is set to true.
The currentFailureCount is the number of times the scheduled service has failed. It is reset to zero when the scheduled service is restarted manually.
The maximumFailureCount specifies the maximum number of times the service can fail before it is transitioned into the FAILED state, and it is not automatically restarted again. Note that you can restart a scheduled service any time manually. By default, it is set to Integer.MAX_VALUE.
EXPONENTIAL_BACKOFF_STRATEGY
LINEAR_BACKOFF_STRATEGY
LOGARITHMIC_BACKOFF_STRATEGY
Exponential: Math.exp(currentFailureCount)
Linear: currentFailureCount
Logarithmic: Math.log1p(currentFailureCount)
Exponential: period + (period * Math.exp(currentFailureCount)
Linear: period + (period * currentFailureCount)
Logarithmic: period + (period * Math.log1p(currentFailureCount))
The cumulativePeriod is a Duration, which is the time between the current failed run and the next run. Its value is computed using the backoffStrategy property. It is reset upon a successful run of the scheduled service. Its value can be capped using the maximumCumulativePeriod property.
Listening to ScheduledService Transition Events
The ScheduledService goes through the same transition states as the Service. It goes through the READY, SCHEDULED, and RUNNING states automatically after a successful run. Depending on how the scheduled service is configured, it may go through the same state transitions automatically after a failed run.
You can listen to the state transitions and override the transition-related methods (ready(), running(), failed(), etc.) as you can for a Service. When you override the transition-related methods in a ScheduledService subclass, make sure to call the super method to keep your ScheduledService working properly.
The Prime Finder ScheduledService Example
Let us use the PrimeFinderTask with a ScheduledService. Once started, the ScheduledService will keep rerunning forever. If it fails five times, it will quit by transitioning itself to the FAILED state. You can cancel and restart the service manually any time.
Using a ScheduledService to Run a Task
Summary
Java (including JavaFX) GUI applications are inherently multithreaded. Multiple threads perform different tasks to keep the UI in sync with the user actions. JavaFX, like Swing and AWT, uses a single thread, called the JavaFX Application Thread, to process all UI events. The nodes representing the UI in a scene graph are not thread-safe. Designing nodes that are not thread-safe has advantages and disadvantages. They are faster, as no synchronization is involved. The disadvantage is that they need to be accessed from a single thread to avoid being in an illegal state. JavaFX puts a restriction that a live scene graph must be accessed from one and only one thread, the JavaFX Application Thread. This restriction indirectly imposes another restriction that a UI event should not process a long-running task, as it will make the application unresponsive. The user will get the impression that the application is hung. The JavaFX concurrency framework is built on top of the Java language concurrency framework keeping in mind that it will be used in a GUI environment. The framework consists of one interface, four classes, and one enum. It provides a way to design a multithreaded JavaFX application that can perform long-running tasks in worker threads, keeping the UI responsive.
An instance of the Worker interface represents a task that needs to be performed in one or more background threads. The state of the task is observable from the JavaFX Application Thread. The Task, Service, and ScheduledService classes implement the Worker interface. They represent different types of tasks. They are abstract classes.
An instance of the Task class represents a one-shot task. A Task cannot be reused.
An instance of the Service class represents a reusable task.
The ScheduledService class inherits from the Service class. A ScheduledService is a task that can be scheduled to run repeatedly after a specified interval.
The constants in the Worker.State enum represent different states of a Worker. An instance of the WorkerStateEvent class represents an event that occurs as the state of a Worker changes. You can add event handlers to all three types of tasks to listen to the change in their states.
The next chapter will discuss how to incorporate audios and videos in JavaFX applications.