dcsimg
August 14, 2018
Hot Topics:

Multithreading in JavaFX

  • July 16, 2018
  • By Manoj Debnath
  • Send Email »
  • More Articles »

Almost all GUI platforms use a single threaded event dispatching model and JavaFX is no exception. JavaFX has a unique set of challenges when dealing with multithreaded programming. The reason is that JavaFX is primarily designed to work in a more or less linear fashion. All user interface events are processed in the JavaFX Application Thread. To take full advantage of modern multicore machines, JavaFX should be able to leverage multithreading of the Java programming language. This article attempts to explain the principles of multithreading under the periphery of JavaFX programming.

JavaFX Scene Graph Is Not Thread-safe

JavaFX UI design begins with a Stage. It is the top-level UI container. However, the actual realization of the Stage depends upon the platform on which it is deployed such as a Web page, a tablet, or a desktop. A JavaFX Scene is played on the stage on which actors represented by the nodes visually interact with each other. The Scene is the container for all content in a scene graph. The main problems of multithreading in JavaFX is that the scene graph itself is not thread-safe. It is modeled to execute on the single JavaFX Application Thread. The constructor and the initialization method init() is called in the JavaFX-Launcher thread. The start() and stop() methods are invoked in the JavaFX Application Thread. The events also are processed on the JavaFX Application Thread. Therefore, any live manipulation on the scene must be done on the main Application Thread alone.

The nodes that are not attached to a live scene may be created and manipulated in other threads. But, once they are attached, they cannot be manipulated by multiple threads without the risk of incorrect result and, in consequence, might corrupt the scene graph. Any long-running tasks applied on the main application thread are bound to freeze the user interface. This is clearly not acceptable from the point of view of multithreaded programming. Thread safety in a JavaFX application cannot be achieved by synchronizing thread actions. We must ensure that the programs that manipulate the scene graph must do so only from the JavaFX Application Thread. Therefore, multithreading in JavaFX has to be handled in a different manner.

Thread Confinement

Thread confinement is a technique applied with JavaFX to realize multithreading. It is a technique that allows only one thread to access the thread-unsafe part of the code, thus ensuring a simple way to achieve tread safety. This, however, is a common technique applied in many other areas of Java programming. Any data in thread confinement is also called thread local. It is thread-safe because races cannot exist in a thread local environment and the data does not need to be locked. This type of thread safety can be seen with pooled JDBC objects. In a typical server application, a thread acquires a connection object from the pool of connections. It uses that connection to process on a single request cycle and return it back to the pool. The pool does not dispense the same connection object to any other thread until the reference to it has been returned. Therefore, the pattern is that the connection management implicitly confines the connection object to a particular thread and reserves or confines it for the duration of the request. This mechanism of thread management, when applied to JavaFX programming, proves to be quite useful.

Implementing Multithreading in JavaFX

Although some minor and brief tasks applied on the heavy-duty JavaFX application thread in sequence with GUI component manipulation is acceptable, this is true only when they cannot be avoided altogether or have a predictable consequence. However, if the task is lengthy, and in response to a user interaction, it must be dealt in a separate thread because a JavaFX application thread cannot render controls or respond to events while the thread is bound to that task. This certainly causes the GUI to freeze and become unresponsive. Delegating a long-running task to a separate thread frees the JavaFX Application Thread to remain responsive and can continue managing GUI interaction. But, nonetheless, the GUI must be updated according to the result obtained from the computation of the running thread.

The runLater Method

JavaFX supplies multiple mechanisms for updating the GUI associated with the scene graph from other threads. One technique is to invoke the static void runLater (Runnable runnable) method defined in the Platform class. According to Java API documentation, this method runs the specified Runnable on the JavaFX Application Thread at some unspecified time in the future. This method, which may be called from any thread, will post the Runnable to an event queue and then return immediately to the caller. But, there is a caution that the application should avoid flooding with too many pending Runnables; otherwise, this may make the application unresponsive. Therefore, such Runnables should perform only small updates to allay the risk.

Using Task and Worker

Any computation intensive tasks must be decoupled from the JavaFX's main application thread by using separate worker threads. JavaFX provides a complete package to deal with the issues of multithreading and concurrency. There is an interface called Worker, an abstract class called Task, and ScheduledService for this purpose. The Task is basically a Worker implementation, ideal for implementing long running computation. The Task class extends FutureTask, and, as a result, supports Runnable, Future, and RunnableFuture interfaces as well. Due to its legacy/inheritance, this class can be used in various ways.

Let's try a quick example to illustrate the idea of implementing long-running task separately using JavaFX.

A Quick Example

package org.mano.example;

import javafx.concurrent.Task;

public class FiboCalcTask extends Task<Long> {

   private final int n;

   public FiboCalcTask(int n) {
      this.n = n;
   }

   @Override
   protected Long call() throws Exception {
      updateMessage("    Processing... ");
      long result = fibonacci(n);
      updateMessage("    Done.  ");
      return result;
   }

   public long fibonacci(long number) {
      if (number == 0 || number == 1)
         return number;
      else
         return fibonacci(number - 1) + fibonacci(number - 2);
   }
}


package org.mano.example;

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

import javafx.application.Application;
import javafx.event.ActionEvent;
import javafx.event.EventHandler;
import javafx.geometry.Insets;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.control.Label;
import javafx.scene.control.TextField;
import javafx.scene.layout.BorderPane;
import javafx.scene.layout.GridPane;
import javafx.stage.Stage;

public class Main extends Application {

   private long n1 = 0;
   private long n2 = 1;
   private int num = 1;

   public static void main(String[] args) {
      Application.launch(args);
   }

   @Override
   public void start(Stage stage) throws Exception {
      BorderPane root=new BorderPane();
      root.setCenter(createInterface());
      Scene scene = new Scene(root, 300, 275);
      stage.setTitle("Multithreading in JavaFX");
      stage.setScene(scene);
      stage.show();
   }

   private GridPane createInterface() {
      GridPane gp = new GridPane();
      TextField tf1 = new TextField();
      Button btnStart = new Button("Start");
      Button btnNext = new Button("Next Fibonacci Number");
      Label lbl1 = new Label();
      Label lbl2 = new Label();
      Label lbl3 = new Label();
      Label lbl4 = new Label();

      btnStart.setOnAction(new
            EventHandler<ActionEvent>() {
         @Override
         public void handle(ActionEvent event) {
            try {
               int ival = Integer.parseInt(tf1.getText());
               FiboCalcTask task = new FiboCalcTask(ival);
               lbl1.textProperty().bind(task.messageProperty());

               task.setOnRunning((succeesesEvent) -> {
                  btnStart.setDisable(true);
                  lbl2.setText("");
               });

               task.setOnSucceeded((succeededEvent) -> {
                  lbl2.setText(task.getValue().toString());
                  btnStart.setDisable(false);
               });

               ExecutorService executorService
                  = Executors.newFixedThreadPool(1);
               executorService.execute(task);
               executorService.shutdown();

            } catch (NumberFormatException e) {
               tf1.setText("Enter a number");
               tf1.selectAll();
               tf1.requestFocus();
            }
         }
      });

      btnNext.setOnAction(new EventHandler<ActionEvent>() {
         @Override
         public void handle(ActionEvent event) {
            lbl3.setText(num+"the Fibonacci number is = ");
            lbl4.setText(String.valueOf(n2));
            long temp = n1 + n2;
            n1 = n2;
            n2 = temp;
            ++num;
         }
      });

      gp.add(new Label("Enter a number: "), 0, 0);
      gp.add(tf1, 1, 0);
      gp.add(lbl1, 2, 0);
      gp.add(btnStart, 0, 1);
      gp.add(lbl2, 1, 1);
      gp.add(lbl3, 0, 2);
      gp.add(lbl4, 1, 2);
      gp.add(btnNext, 0, 3);
      gp.setPadding(new Insets(10.0,10.0,10.0,10.0));

      return gp;
   }
}

Conclusion

This article showed the basics of JavaFX multithreading. It also tried to show the exact point where multithreading in JavaFX is a bit of a different take with respect to multithreading in regular Java. The core classes of multithreading in JavaFX are defined in the javafx.concurrent package. Consult the API documentation for more detail on the classes and interfaces used in the preceding code.






Comment and Contribute

 


(Maximum characters: 1200). You have characters left.

 

 


Enterprise Development Update

Don't miss an article. Subscribe to our newsletter below.

By submitting your information, you agree that developer.com may send you developer offers via email, phone and text message, as well as email offers about other products and services that developer believes may be of interest to you. developer will process your information in accordance with the Quinstreet Privacy Policy.

Sitemap

Thanks for your registration, follow us on our social networks to keep up-to-date