Using threads in a Java FX UI that should return a result

I am probably missing something here, but I'll try and explain what I want to achieve and then someone please tell me that I am doing it wrong(which I am :) ) and point me in the right direction?

I am using JavaFX 2.0, but I think this problem would lend itself to Swing, or any UI framework.

I want to develope a simple splash screen for my application, when the splash screen starts, I want to have a message label that will be used to update a user on whats happening, in regards to configuring up the back end of the application. My application start up has 2 steps, the first step uses Spring to initialise the Application Context, which in turn initialises the DB (JPA 2.0/Hibernate/etc). The second part of my application start up process will query the DB for the initial data which will be used to populate the UI. Both these steps need to be complete before I can close the splash screen, and between each step I want to update the label in the splash screen to let a user know which stage is being done at that time.

I have broken this down into the following simple program which uses JavaFX and a button, when the button is pressed a new thread is created, that starts another class, which just performs some count to an abitary value, and then another another thread is created to simlate the second step of the start up process, But my issue is here that the second thread attempts to run before the first thread has finished, and as a result runs into a NPE.

Below is a breakdown of some simple code that highlights this issue:

public class Test extends Application
{
    private LongTester lt;
    public static void main(String[] args)
    {
        launch(args);
    }

    @Override
    public void start(Stage primaryStage)
    {
        Button btn = new Button();
        final Label lblText = new Label("Starting");

        btn.setText("Say 'Hello World'");
        btn.setOnAction(new EventHandler<ActionEvent>()
        {
            @Override
            public void handle(ActionEvent event)
            {                
                new Thread(new ConstructorRunnable()).start();

                lblText.setText("More Loading?");

                new Thread(new MethodRunnable()).start();

                lblText.setText("Finished");
            }
        });

        HBox root = new HBox();
        root.getChildren().add(btn);

        Scene scene = new Scene(root, 300, 250);

        primaryStage.setTitle("Hello World!");
        primaryStage.setScene(scene);
        primaryStage.show();
    }

    private class ConstructorRunnable implements Runnable
    {
        @Override
        public void run()
        {
            lt = new LongTester();
        }
    }

    private class MethodRunnable implements Runnable
    {
        @Override
        public void run()
        {
            lt.countAgain();
        }
    }

    private class LongTester
    {
        public LongTester()
        {
            for (int idx = 0; idx < 1000000; idx++)
            {
                System.out.println("Constructor: " + idx);
            }
        }

        public Boolean countAgain()
        {
            for (int idx = 0; idx < 1000000; idx++)
            {
                System.out.println("Method: " + idx);
            }
            return Boolean.TRUE;
        } 
    }
}

Can anyone point out my mistake?

Answers


I'd advise using a Task to execute your startup tasks and message progress back to your splash screen (similar to the approach in this sample created for a prior stackoverflow question on splash screens). If you want stuff in your task to run sequentially, just use one thread for the task rather than two.

Sample code for your task might look something like:

final Task<Data> initTask = new Task() {
  @Override protected Data call() throws InterruptedException {
    updateMessage("Initializing Application");
    MyApp.initializeAppContext(); 
    updateMessage("Loading Data");
    Data data = DB.loadData(); 
    updateMessage("Data Loaded");

    return data;
  }

showSplash(initStage, initTask);
new Thread(initTask).start();
showMainStage(initTask.valueProperty());

To fix your problem, you can use a CountDownLatch This can be used the following way:

private class ConstructorRunnable implements Runnable {
   CountDownLatch gate_ = null;
   public ConstructorRunnable(CountDownLatch gate){
      gate_ = gate;
   } 
   @Override
   public void run() {
      lt = new LongTester();
      gate_.countDown();  // Signal the second thread to start
   }
}

private class MethodRunnable implements Runnable{
  CountDownLatch gate_ = null;
  public MethodRunnable(CountDownLatch gate){
      gate_ = gate;
  }
  @Override
  public void run(){
     CountDownLatch.await(); // Wait for the first thread to finish
     lt.countAgain();
  }
}

This can now be used like this:

CountDownLatch gate = new CountDownLatch(1);
new Thread(new ConstructorRunnable(gate)).start();
lblText.setText("More Loading?");
new Thread(new MethodRunnable(gate)).start();
lblText.setText("Finished");

On a side note: as your tasks are sequential, why is there a need for having several threads? Threads are made to run multiple tasks running in parallel and your case does not make a use case for threads as these operations are sequential


Need Your Help

Best Way to Install and Maintain the Dependency?

gcc rpm centos6 yum

I am a bit new to this kind of administration stuffs -- I would like to build GCC 4.8.2 (just an example) myself, and I would like some how makes yum realize that there is a package newer than what...

jquery vertical center multiple images of different dimensions

jquery alignment

I have a fluid grid layout, 3 rows, each with 3 different % width blocks. Essentially this is a 3x3 grid that expands both horz and vert in the viewport, albeit different width blocks. In each block,