在JavaFX中切换按钮的操作是原子的吗?

时间:2014-10-02 08:03:07

标签: multithreading javafx synchronization togglebutton

JavaFX操作中的切换按钮将由2个单独的线程访问。

1 一旦用户点击(切换按钮状态),就会调用一个线程 a)在操作系统中做一些事情 b)检查(a)是否成功 c)在成功/退出时退出,并在失败时将切换按钮返回到先前的状态

2 另一个线程将监视与先前操作异步的事件,并且在特定事件的情况下,它将更改按钮状态。

在锁定按钮状态方面,我是否需要在线程 1 2 之间提供同步?

编辑:James_D提出的想法似乎是合理的,但我只是想提出一个替代方案(其有效性仍有待证明)。 如何使用同步代码块,并使用锁定对特定按钮的引用,例如:

// getting the reference to the button 
@FXML
private ToggleButton tButtonToBeSynchronized

// Thread1
synchronized(tButtonToBeSynchronized) {
// do stuff with button upon user click 
}

// Thread2
synchronized(tButtonToBeSynchronized) {
// poll system every X seconds
// when asynchronous event occurs (not related to UI events)
// update tButtonToBeSynchronized state
}

如果不同的Controller类调用它们会有用吗? (假设引用tButtonToBeSynchronized是通过引用传递的 - 而不是FXML框架的值?

1 个答案:

答案 0 :(得分:2)

与大多数UI工具包一样,JavaFX采用单线程模型。您应该只从FX Application Thread访问属于场景图的一部分节点的状态。因此,切换按钮不是原子操作,并且您描述的代码不能保证按照您当前设置的方式工作。在Java 8中,它可能会抛出RuntimeException

JavaFX提供了与后台线程实现互操作的功能。其中最低级别为Platform.runLater(Runnable r),在FX应用程序线程上执行r。因此,您的监视器线程(问题中的第2项)应该使用

更改切换按钮的状态
Platform.runLater( () -> toggleButton.setSelected(...) );

还有javafx.concurrent API。这提供了一个Task类,它同时充当Runnablejava.util.concurrent.FutureTask,另外还有一组回调方法,用于提交要在FX应用程序线程上执行的代码在Task生命周期的各个阶段。

所以你应该在你的问题中实现第1项:

ExecutorService exec = ... ; // e.g. Executors.newCachedThreadPool();

toggleButton.selectedItemProperty().addListener((obs, wasSelected, isNowSelected) -> {
    if (isNowSelected) {
        Task<Void> task = new Task<Void>() {
            @Override
            public Void call() throws Exception {
                // do something on OS
                // throw exception if failed
                return null ;
            }
        };
        task.setOnFailed(event -> toggleButton.setSelected(wasSelected));
        exec.submit(task);
    }
});

如果您希望返回表示成功或失败的值,则可以执行

Task<Boolean> task = new Task<Boolean>() {
    @Override
    public Boolean call() {
        // do work...
        boolean successful =  ... ;
        return successful ;
    }
};
task.setOnSucceeded( event -> {
    boolean wasSuccessful = task.getValue();
    // ...
});