JavaFX

JavaFX Concurrency Example

This is a JavaFX Concurrency Example. Java 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 JavaFX Application Thread, to process all UI events.
 
 
 
 
 
 
 
 

 
The following table shows an overview of the whole article:

The following examples uses Java SE 8 and JavaFX 2.2.

1. Introduction

The nodes representing 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 following examples will show the problem in detail.

2. The given Problem

2.1 The Code

FxConcurrencyExample1.java

import javafx.application.Application;
import javafx.event.ActionEvent;
import javafx.event.EventHandler;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.control.Label;
import javafx.scene.control.TextArea;
import javafx.scene.layout.HBox;
import javafx.scene.layout.VBox;
import javafx.stage.Stage;

public class FxConcurrencyExample1  extends Application
{
	// Create the TextArea
	TextArea textArea = new TextArea();
	
	// Create the Label
	Label statusLabel = new Label("Not Started...");
	
	// Create the Buttons
	Button startButton = new Button("Start");
	Button exitButton = new Button("Exit");
	
	public static void main(String[] args) 
	{
		Application.launch(args);
	}
	
	@Override
	public void start(final Stage stage) 
	{
		// Create the Event-Handlers for the Buttons
		startButton.setOnAction(new EventHandler <ActionEvent>() 
		{
            public void handle(ActionEvent event) 
            {
            	runTask();
            }
        });

		exitButton.setOnAction(new EventHandler <ActionEvent>() 
		{
            public void handle(ActionEvent event) 
            {
            	stage.close();
            }
        });
		
		// Create the ButtonBox		
		HBox buttonBox = new HBox(5, startButton, exitButton);
		
		// Create the VBox
		VBox root = new VBox(10, statusLabel, buttonBox, textArea);
		
		// Set the Style-properties of the VBox
		root.setStyle("-fx-padding: 10;" +
				"-fx-border-style: solid inside;" +
				"-fx-border-width: 2;" +
				"-fx-border-insets: 5;" +
				"-fx-border-radius: 5;" +
				"-fx-border-color: blue;");

		// Create the Scene
		Scene scene = new Scene(root,400,300);
		// Add the scene to the Stage
		stage.setScene(scene);
		// Set the title of the Stage
		stage.setTitle("A simple Concurrency Example");
		// Display the Stage
		stage.show();		
	}
	
	public void runTask() 
	{
		for(int i = 1; i <= 10; i++) 
		{
			try 
			{
				String status = "Processing " + i + " of " + 10;
				statusLabel.setText(status);
				textArea.appendText(status+"\n");
				Thread.sleep(1000);
			}
			catch (InterruptedException e) 
			{
				e.printStackTrace();
			}
		}
	}	
}

The above program displays a window as shown in the GUI. It contains three controls.

  • A Label to display the progress of a task
  • A Start button to start the task
  • An Exit button to exit the application

The program is very simple. When you click the Start Button, a task lasting for 10 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 1 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 10 seconds for the task to finish.

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 don´t see the status updated in the Label.

public void runTask() 
{
	for(int i = 1; i <= 10; i++) 
	{
		try 
		{
			String status = "Processing " + i + " of " + 10;
			statusLabel.setText(status);
			textArea.appendText(status+"\n");
			Thread.sleep(1000);
		}
		catch (InterruptedException e) 
		{
			e.printStackTrace();
		}
	}
}	

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.

2.2 The GUI

The following image shows the GUI after clicking the Start Button:

A simple JavaFX Concurrency Example
A simple JavaFX Concurrency Example

The following image shows the GUI of the above program after execution:

A simple JavaFX Concurrency Example
A simple JavaFX Concurrency Example

3. A first Solution Approach

3.1 The Code

FxConcurrencyExample2.java

import javafx.application.Application;
import javafx.event.ActionEvent;
import javafx.event.EventHandler;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.control.Label;
import javafx.scene.control.TextArea;
import javafx.scene.layout.HBox;
import javafx.scene.layout.VBox;
import javafx.stage.Stage;

public class FxConcurrencyExample2  extends Application
{
	// Create the TextArea
	TextArea textArea = new TextArea();
	
	// Create the Label
	Label statusLabel = new Label("Not Started...");

	// Create the Buttons
	Button startButton = new Button("Start");
	Button exitButton = new Button("Exit");
	
	public static void main(String[] args) 
	{
		Application.launch(args);
	}
	
	@Override
	public void start(final Stage stage) 
	{
		// Create the Event-Handlers for the Buttons
		startButton.setOnAction(new EventHandler <ActionEvent>() 
		{
            public void handle(ActionEvent event) 
            {
            	startTask();
            }
        });

		exitButton.setOnAction(new EventHandler <ActionEvent>() 
		{
            public void handle(ActionEvent event) 
            {
            	stage.close();
            }
        });
		
		// Create the ButtonBox		
		HBox buttonBox = new HBox(5, startButton, exitButton);
		
		// Create the VBox
		VBox root = new VBox(10, statusLabel, buttonBox,textArea);
		
		// Set the Style-properties of the VBox
		root.setStyle("-fx-padding: 10;" +
				"-fx-border-style: solid inside;" +
				"-fx-border-width: 2;" +
				"-fx-border-insets: 5;" +
				"-fx-border-radius: 5;" +
				"-fx-border-color: blue;");

		// Create the Scene
		Scene scene = new Scene(root,400,300);
		// Add the scene to the Stage
		stage.setScene(scene);
		// Set the title of the Stage
		stage.setTitle("A simple Concurrency Example");
		// Display the Stage
		stage.show();				
	}
	
	public void startTask() 
	{
		// Create a Runnable
		Runnable task = new Runnable()
		{
			public void run()
			{
				runTask();
			}
		};
		
		// Run the task in a background thread
		Thread backgroundThread = new Thread(task);
		// Terminate the running thread if the application exits
		backgroundThread.setDaemon(true);
		// Start the thread
		backgroundThread.start();
	}
		
	public void runTask() 
	{
		for(int i = 1; i <= 10; i++) 
		{
			try 
			{
				String status = "Processing " + i + " of " + 10;
				statusLabel.setText(status);
				textArea.appendText(status+"\n");
				Thread.sleep(1000);
			}
			catch (InterruptedException e) 
			{
				e.printStackTrace();
			}
		}
	}		
}

The above program is your first, incorrect attempt to provide a solution. The ActionEvent handler for the Start Button calls the startTask() method, which creates a new thread and runs the runTask() method in the new thread.

Run the program and click the Start Button. A runtime exception is thrown. The partial stack trace of the exception is as follows:

Exception in thread "Thread-4" java.lang.IllegalStateException: Not on FX application thread; currentThread = Thread-4
	at com.sun.javafx.tk.Toolkit.checkFxUserThread(Toolkit.java:236)
	at com.sun.javafx.tk.quantum.QuantumToolkit.checkFxUserThread(QuantumToolkit.java:423)
	at javafx.scene.Parent$2.onProposedChange(Parent.java:367)
	at com.sun.javafx.collections.VetoableListDecorator.setAll(VetoableListDecorator.java:113)
	at com.sun.javafx.collections.VetoableListDecorator.setAll(VetoableListDecorator.java:108)
	at com.sun.javafx.scene.control.skin.LabeledSkinBase.updateChildren(LabeledSkinBase.java:575)
	at com.sun.javafx.scene.control.skin.LabeledSkinBase.handleControlPropertyChanged(LabeledSkinBase.java:204)
	at com.sun.javafx.scene.control.skin.LabelSkin.handleControlPropertyChanged(LabelSkin.java:49)
	at com.sun.javafx.scene.control.skin.BehaviorSkinBase.lambda$registerChangeListener$61(BehaviorSkinBase.java:197)
	at com.sun.javafx.scene.control.MultiplePropertyChangeListenerHandler$1.changed(MultiplePropertyChangeListenerHandler.java:55)
	at javafx.beans.value.WeakChangeListener.changed(WeakChangeListener.java:89)
	at com.sun.javafx.binding.ExpressionHelper$SingleChange.fireValueChangedEvent(ExpressionHelper.java:182)
	at com.sun.javafx.binding.ExpressionHelper.fireValueChangedEvent(ExpressionHelper.java:81)
	at javafx.beans.property.StringPropertyBase.fireValueChangedEvent(StringPropertyBase.java:103)
	at javafx.beans.property.StringPropertyBase.markInvalid(StringPropertyBase.java:110)
	at javafx.beans.property.StringPropertyBase.set(StringPropertyBase.java:144)
	at javafx.beans.property.StringPropertyBase.set(StringPropertyBase.java:49)
	at javafx.beans.property.StringProperty.setValue(StringProperty.java:65)
	at javafx.scene.control.Labeled.setText(Labeled.java:145)
	at FXConcurrency.FxConcurrencyExample2.runTask(FxConcurrencyExample2.java:101)
	at FXConcurrency.FxConcurrencyExample2$3.run(FxConcurrencyExample2.java:82)
	at java.lang.Thread.run(Thread.java:745)

The following statement in the runTask() method generated the exception:

statusLabel.setText(status);

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.

How do you access a live Scene Graph from a thread other than the JavaFX Application Thread?

The simple answer is that you cannot. The complex answer is that when a thread wants to access a live Scene Graph, it needs to run the part of the code that accesses the Scene Graph in the JavaFX Application Thread.

The Platform class in the javafx.application package provides two static methods to work with the JavaFX Application Thread.

  • 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.

3.2 The GUI

The following image shows the result of the program during execution:

A simple JavaFX Concurrency Example
A simple JavaFX Concurrency Example

4. The Solution

4.1 The Code

FxConcurrencyExample3.java

import javafx.application.Application;
import javafx.application.Platform;
import javafx.event.ActionEvent;
import javafx.event.EventHandler;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.control.Label;
import javafx.scene.control.TextArea;
import javafx.scene.layout.HBox;
import javafx.scene.layout.VBox;
import javafx.stage.Stage;

public class FxConcurrencyExample3  extends Application
{
	// Create the TextArea
	TextArea textArea = new TextArea();
	
	// Create the Label
	Label statusLabel = new Label("Not Started...");

	// Create the Buttons
	Button startButton = new Button("Start");
	Button exitButton = new Button("Exit");
	
	public static void main(String[] args) 
	{
		Application.launch(args);
	}
	
	@Override
	public void start(final Stage stage) 
	{
		// Create the Event-Handlers for the Buttons
		startButton.setOnAction(new EventHandler <ActionEvent>() 
		{
            public void handle(ActionEvent event) 
            {
            	startTask();
            }
        });

		exitButton.setOnAction(new EventHandler <ActionEvent>() 
		{
            public void handle(ActionEvent event) 
            {
            	stage.close();
            }
        });
		
		// Create the ButtonBox		
		HBox buttonBox = new HBox(5, startButton, exitButton);
		
		// Create the VBox
		VBox root = new VBox(10, statusLabel, buttonBox, textArea);
		
		// Set the Style-properties of the VBox
		root.setStyle("-fx-padding: 10;" +
				"-fx-border-style: solid inside;" +
				"-fx-border-width: 2;" +
				"-fx-border-insets: 5;" +
				"-fx-border-radius: 5;" +
				"-fx-border-color: blue;");

		// Create the Scene
		Scene scene = new Scene(root,400,300);
		// Add the scene to the Stage
		stage.setScene(scene);
		// Set the title of the Stage
		stage.setTitle("A simple Concurrency Example");
		// Display the Stage
		stage.show();				
	}
	
	public void startTask() 
	{
		// Create a Runnable
		Runnable task = new Runnable()
		{
			public void run()
			{
				runTask();
			}
		};

		// Run the task in a background thread
		Thread backgroundThread = new Thread(task);
		// Terminate the running thread if the application exits
		backgroundThread.setDaemon(true);
		// Start the thread
		backgroundThread.start();
	}	
	
	public void runTask() 
	{
		for(int i = 1; i <= 10; i++) 
		{
			try 
			{
				// Get the Status
				final String status = "Processing " + i + " of " + 10;
				
				// Update the Label on the JavaFx Application Thread		
				Platform.runLater(new Runnable() 
				{
		            @Override 
		            public void run() 
		            {
		            	statusLabel.setText(status);
		            }
		        });
		
				textArea.appendText(status+"\n");
		
				Thread.sleep(1000);
			}
			catch (InterruptedException e) 
			{
				e.printStackTrace();
			}
		}
	}	
}

Let us fix the problem in the application. The above program is the correct implementation of the logic to access the live Scene Graph.

The program replaces the statement

statusLabel.setText(status);

in the Java class with the statement

// Update the Label on the JavaFx Application Thread		
Platform.runLater(new Runnable() 
{
    @Override 
    public void run() 
    {
	statusLabel.setText(status);
    }
});

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 a 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 a 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.

I will discuss the framework more in detail in a special article.

4.2 The GUI

The following GUI shows a snapshot of the window displayed by the program.

A simple JavaFX Concurrency Example
A simple JavaFX Concurrency Example

5. Download Java Source Code

This was an example of javafx.concurrent

Download
You can download the full source code of this example here: JavaFxConcurrencyExample.zip

Andreas Pomarolli

Andreas has graduated from Computer Science and Bioinformatics at the University of Linz. During his studies he has been involved with a large number of research projects ranging from software engineering to data engineering and at least web engineering. His scientific focus includes the areas of software engineering, data engineering, web engineering and project management. He currently works as a software engineer in the IT sector where he is mainly involved with projects based on Java, Databases and Web Technologies.
Subscribe
Notify of
guest

This site uses Akismet to reduce spam. Learn how your comment data is processed.

6 Comments
Oldest
Newest Most Voted
Inline Feedbacks
View all comments
Ariel
Ariel
6 years ago

Excellent article!

Aleksandr
Aleksandr
6 years ago

Thank you! I am very grateful to you. I’m tried to find a solution to such a problem for a long time. Your article is excellent.

jot.K
jot.K
6 years ago

Hi, Thanks for the post. I was wondering if there is a way, if we want to make different threads in the same class, for instance, I want to run different exe files using different threads and all the threads have to wait for the first completion. Can we make multiple Start task() and then write run() for different threads.

Paulo
Paulo
5 years ago

Hi, nice article, why does the line : statusLabel.setText(status); raises and exception but textArea.appendText(status+”\n”); does not?

Shouldn’t we be updating both the label and the textArea from the JavaFX UI thread?

Thanks
Paulo

Rizwan Hasan
Rizwan Hasan
5 years ago

This article saved my day

James
James
5 years ago

The final solution is incorrect; the call to textArea.appendText(status+”\n”); should also be executed inside the Platform.runLater(...) runnable block. (Text areas do not perform thread checks, however they are still not thread safe, so while this doesn’t throw an Exception, it has the potential to leave the text area in an inconsistent state.)

Back to top button