0

我在开发javafx时遇到一个问题,我发现latch在JavaFx中没有效果,例如在下面的代码中:

public class JavafxLatchDemo1 extends Application {

    @Override
    public void start(Stage primaryStage) throws InterruptedException {
        CountDownLatch latch = new CountDownLatch(1);
        TextArea txtOut = new TextArea();

        StackPane root = new StackPane();
        root.getChildren().add(txtOut);

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

        //invoke rpc function
        Callable<Integer> fibCall = new fibCallable(latch, txtOut);
        FutureTask<Integer> fibTask = new FutureTask<Integer>(fibCall);
        Thread fibThread = new Thread(fibTask);
        fibThread.start();
        latch.await(); //阻塞等待计数为0 


        txtOut.appendText("\n Say 'Hello World'");
        primaryStage.setTitle("Hello World!");
        primaryStage.setScene(scene);
        primaryStage.show();
    }

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

class fibCallable implements Callable<Integer>{        
    CountDownLatch latch;
    TextArea txtInput;

    fibCallable(CountDownLatch mylatch, TextArea txtIn){
        latch = mylatch;
        txtInput = txtIn;
    }

    @Override
    public Integer call() throws Exception {
        int temp1=1,temp2=0;

        System.out.println("Client will pay money for eshop");

        for(int i=0; i<10; i++){
            temp1 = temp1 + temp2;
            temp2 = temp1;
        }
        System.out.println("Client already decide to pay money for eshop");

        Platform.runLater(()->{
            txtInput.appendText("\nWhy, am I first?");

        });
        latch.countDown(); //计数减1 

        return (new Integer(temp1));
    }  
}

由于我设置了一个闩锁来停止 JavaFx 主线程latch.await();,并希望可调用线程fibCallable首先输出内容:所以我期望:

Why, am I first?
 Say 'Hello World'

但实际输出相反:

Say 'Hello World'
Why, am I first?

为什么?和解决方案?

4

1 回答 1

2

Platform.runLater()提交要在 FX 应用程序线程上执行的可运行文件。该start()方法也在 FX 应用程序线程上执行。因此,在该线程上已经执行的任何操作完成之前,Runnable您提交的Platform.runLater()内容无法执行。

因此,您fibThread在后台启动您的程序,然后立即等待闩锁:这会阻塞 FX 应用程序线程。fibThread做一些工作,然后提交对(阻塞的)FX 应用程序线程的调用。然后fibThread释放闩锁:当前阻塞的 FX 应用程序线程解除阻塞并完成当前方法调用(将文本附加"Say Hello World"到文本区域并显示阶段),并在之后的某个时间点,提交给的可运行对象Platform.runLater()在同一线程上执行。

“快速而肮脏”的修复只是将第二次调用包装txtOut.appendText(...)在另一个中Platform.runLater()

Platform.runLater(() -> txtOut.appendText("\n Say 'Hello World'"));

这可以保证工作,因为传递给Platform.runLater()的可运行对象保证按照它们传递的顺序执行,并且倒计时锁存器在两次调用之间建立了“先发生”的关系Platform.runLater()

但是请注意,您正在通过调用阻塞 FX 应用程序线程latch.await(),这是不好的做法(并且会延迟舞台的显示,直到后台线程完成)。您真的应该将调用与另一个后台线程中latch.await()的第二个一起调用。Platform.runLater()另请注意,您根本不需要闩锁,因为您已经有了 a FutureTask,您可以等待它的结果(这相当于等待闩锁)。所以你可以做

import java.util.concurrent.Callable;
import java.util.concurrent.FutureTask;

import javafx.application.Application;
import javafx.application.Platform;
import javafx.scene.Scene;
import javafx.scene.control.TextArea;
import javafx.scene.layout.StackPane;
import javafx.stage.Stage;

public class JavafxLatchDemo1 extends Application {

    @Override
    public void start(Stage primaryStage) throws InterruptedException {
//        CountDownLatch latch = new CountDownLatch(1);
        TextArea txtOut = new TextArea();

        StackPane root = new StackPane();
        root.getChildren().add(txtOut);

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

        //invoke rpc function
//        Callable<Integer> fibCall = new fibCallable(latch, txtOut);
        Callable<Integer> fibCall = new fibCallable(txtOut);
        FutureTask<Integer> fibTask = new FutureTask<Integer>(fibCall);
        Thread fibThread = new Thread(fibTask);
        fibThread.start();
//        latch.await(); //阻塞等待计数为0 


        new Thread(() -> {
            try {
                // wait for fibTask to complete:
                fibTask.get();
                // and now append text to text area,
                // but this now must be done back on the FX Application Thread
                Platform.runLater(() -> txtOut.appendText("\n Say 'Hello World'"));
            } catch (Exception ignored) {
                // ignore interruption: thread is exiting anyway....
            }
        }).start();
        primaryStage.setTitle("Hello World!");
        primaryStage.setScene(scene);
        primaryStage.show();
    }

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

    class fibCallable implements Callable<Integer>{        
//        CountDownLatch latch;
        TextArea txtInput;

        fibCallable(TextArea txtIn){
            txtInput = txtIn;
        }

        @Override
        public Integer call() throws Exception {
            int temp1=1,temp2=0;

            System.out.println("Client will pay money for eshop");

            for(int i=0; i<10; i++){
                temp1 = temp1 + temp2;
                temp2 = temp1;
            }

            System.out.println("Client already decide to pay money for eshop");

            Platform.runLater(()->{
                txtInput.appendText("\nWhy, am I first?");

            });
//            latch.countDown(); //计数减1 

            return (new Integer(temp1));
        }  
    }
}

最后,请注意 JavaFX 有自己的并发 API,它直接支持 FX 应用程序线程上的各种回调。这个 API 通常意味着你可以避免用闩锁和锁等弄脏你的手。

于 2017-05-10T18:19:38.093 回答