-1

Here is my code, can someone explain why it works every time?

package dingding;

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.TextField;
import javafx.scene.layout.VBox;
import javafx.scene.layout.HBox;
import javafx.stage.Stage;


public class Dingding extends Application {

    TextField tfAuto = new TextField("0");
    AutoRunThread runner = new AutoRunThread();
    boolean shouldStop = false;

    private class AutoRunThread extends Thread {

        @Override
        public void run() {
            while (true) {
                int i = Integer.parseInt(tfAuto.getText());
                ++i;
                tfAuto.setText(String.valueOf(i));
                try {
                Thread.sleep(1000);
                } catch (Throwable t) {

                }
                if (shouldStop) {
                    runner = null;
                    shouldStop = false;
                    return;
                }
            }
        }
    }

    @Override
    public void start(Stage primaryStage) {

        Button btnStart = new Button("Increment Automatically");
        Button btnStop = new Button("Stop Autotask");

        btnStart.setOnAction(new EventHandler<ActionEvent>() {
            @Override
            public void handle(ActionEvent event) {
                if (runner == null) {
                    runner = new AutoRunThread();
                    runner.setDaemon(true);
                }
                if (runner != null && !(runner.isAlive())) {
                    runner.start();
                }
            }
        });

        btnStop.setOnAction(new EventHandler<ActionEvent>() {
            @Override
            public void handle(ActionEvent event) {
                shouldStop = true;
            }
        });


        VBox rootBox = new VBox();
        HBox autoBox = new HBox();

        autoBox.getChildren().addAll(tfAuto, btnStart, btnStop);

        rootBox.getChildren().addAll(autoBox);

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

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

    /**
     * @param args the command line arguments
     */
    public static void main(String[] args) {
        launch(args);
    }

}
kleopatra
  • 51,061
  • 28
  • 99
  • 211
Mishax
  • 4,442
  • 5
  • 39
  • 63
  • yes, you _must_ update a node that's part of the scenegraph on the fx application thread - no exception. The other way round: as long as it is not yet added, you can change it from any thread – kleopatra Apr 12 '19 at 10:34
  • yes @kleopatra that's what I thought also, my question is why does it work – Mishax Apr 12 '19 at 10:41
  • 1
    it doesn't and if it does, it's accidental ;) – kleopatra Apr 12 '19 at 10:55
  • I've included the code @kleopatra , would appreciate any insights – Mishax Apr 12 '19 at 11:06
  • 2
    don't quite understand what you are after: you __MUST NOT__ change any property of a control that's in the scenegraph. You might get away with it (not every change is guarded against thread violation) but doing so creates instable software that's bound to fail unpredictably and such failures are extremely difficult to debug and fix .. So what's your point? simply don't! – kleopatra Apr 12 '19 at 11:42
  • 3
    Improperly synchronized code doesn't guarantee errors _per se_, but that doesn't mean the code, in a multi-threaded context, is "working"—you're merely getting lucky. When it comes to JavaFX, only some UI-related functions actually check to ensure it's being called by the FX thread. The remaining functions silently let you call them from any thread, but doing so can lead to _undefined behavior_ (e.g. corrupted state, unexpected exceptions, stale values, etc...). – Slaw Apr 12 '19 at 13:25
  • @Slaw perhaps you could put that as an answer to the question – Mishax Apr 15 '19 at 09:43

1 Answers1

3

As I said in my comment, improperly synchronized code doesn't guarantee errors per se. However, that doesn't mean said code, when used in a multi-threaded context, is actually working—you're merely getting lucky. Eventually you'll run into undefined behavior such as corrupted state, stale values, and unexpected exceptions. This is because, without synchronization, actions performed by one thread are not guaranteed to be visible to any other thread. You need a happens-before relationship, better described in the package documentation of java.util.concurrent and this SO question.

JavaFX, like most UI frameworks/toolkits, is single threaded. This means there's a special thread—in this case, the JavaFX Application Thread— that is responsible for all UI related actions1. It is this thread, and this thread only, that must be used to access and/or modify state related to a "live" scene graph (i.e. nodes that are in a scene that's in a window that's showing2). Using any other thread can lead to the undefined behavior described above.

Some UI related functions actually ensure they're being called on the JavaFX Application Thread, usually throwing an IllegalStateException if not. However, the remaining functions will silently let you call them from any thread—but that doesn't mean it's safe to do so. This is done this way, I believe, because checking the thread in every UI related function is a maintenance nightmare and would incur a not-insignificant performance cost.


1. It's slightly more complicated that this; JavaFX also has a "prism render thread" and a "media thread". See Understanding JavaFX Architecture for more information. But note that, from an application developer's point of view, the only thread that matters is the JavaFX Application Thread.

2. This is documented by Node. Note that some nodes, such as WebView, are more restrictive when it comes to threading; this will be documented in the appropriate places.

Slaw
  • 37,820
  • 8
  • 53
  • 80