布尔值不会从Object.getBoolean()更新;

时间:2016-08-18 02:56:15

标签: java javafx jfilechooser filechooser

差不多,我试图编写一个让用户选择文件的简单程序。不幸的是,通过Swing的JFileChooser有点过时,所以我试图使用JavaFX FileChooser。目标是将FileGetter作为线程运行,将文件数据传输到Main Class,然后从那里继续。

主类:

package application;
import java.io.File;
import javafx.application.Application;
import javafx.stage.Stage;
import javafx.scene.Scene;
import javafx.scene.layout.BorderPane;


public class Main {

    public static void main(String[] args) {
        Thread t1 = new Thread(new FileGetter());
        FileGetter fg = new FileGetter();
        t1.start();
        boolean isReady = false;
        while(isReady == false){
            isReady = FileGetter.getIsReady();      
        }
        File file = FileGetter.getFile();

        System.out.println(file.getAbsolutePath());
        ...

    }
}

FileGetter类:

package application;

import java.io.File;
import javafx.application.Application;
import javafx.application.Platform;
import javafx.stage.FileChooser;
import javafx.stage.Stage;
import javafx.scene.Scene;
import javafx.scene.layout.BorderPane;


public class FileGetter extends Application implements Runnable {

    static File file;
    static boolean isReady = false;


    @Override
    public void start(Stage primaryStage) {
        try {

            FileChooser fc = new FileChooser();
            while(file == null){
            file = fc.showOpenDialog(primaryStage);
            }
            isReady = true;
            Platform.exit();
        } catch(Exception e) {
            e.printStackTrace();
        }
    }
    @Override
    public void run() {
       launch();
    }

    public static boolean getIsReady(){
        return isReady;
    }

    public static File getFile(){
        return file;
    }

}

问题是当用户选择文件时,while循环中isReady的值不会更新为true(我之所以这样做是为了防止Main中的代码继续将File设置为null)

非常感谢任何帮助,替代建议或解释为什么会发生这种情况!

3 个答案:

答案 0 :(得分:3)

除特定条件外,java内存模型不要求变量值在不同的线程中相同。

这里发生的是FileGetter线程正在更新自己内存中只能从该线程访问的值,但是你的主线程没有看到更新的值,因为它只看到存储在其自身内存中的变量版本与FileGetter线程的内存不同。每个线程都有自己的内存副本,根据java规范,它完全没问题。

要解决此问题,您只需将volatile修饰符添加到isReady

static volatile boolean isReady = false;

确保从主线程中看到更新后的值。

此外,我建议减少您创建的FileGetter个实例的数量。在您的代码中,创建了3个实例,但只使用了1个实例。

Thread t1 = new Thread(() -> Application.launch(FileGetter.class));
t1.start();
...

答案 1 :(得分:2)

在此代码中

 while(isReady == false){
        isReady = FileGetter.getIsReady();      
 }

没有任何内容可以将FileGetter的状态改为true

答案 2 :(得分:2)

实施此

的最简单方法

为什么不遵循标准的JavaFX生命周期,而不是试图用推车推动马匹?换句话说,让你的Main类成为Application的子类,用start()方法获取文件,然后继续(在后台线程中)与应用程序的其余部分一起? / p>

public class Main extends Application {

    @Override
    public void init() {
        // make sure we don't exit when file chooser is closed...
        Platform.setImplicitExit(false);
    }

    @Override
    public void start(Stage primaryStage) {
        File file = null ;
        FileChooser fc = new FileChooser();
        while(file == null){
            file = fc.showOpenDialog(primaryStage);
        }
        final File theFile = file ;
        new Thread(() -> runApplication(theFile)).start();
    }

    private void runApplication(File file) {
        // run your application here...
    }

}

您的代码有什么问题

如果你真的希望Main类与JavaFX Application类分开(实际上没有意义:一旦你决定使用JavaFX FileChooser,你已经决定你正在编写一个JavaFX应用程序,所以启动类应该是Application)的子类,然后它有点棘手。您的代码有几个问题,其中一些问题在其他答案中得到解决。正如Fabian的回答所示,主要问题是你在没有确保活跃的情况下从多个线程引用FileGetter.isReady。这正是Josh Bloch的Effective Java(第2版第66项)所解决的问题。

您的代码的另一个问题是,您将无法多次使用FileGetter(您不能多次调用launch()),这可能不是问题现在你的代码,但随着开发的进展,几乎可以肯定这个应用程序。问题是你混淆了两个问题:启动FX工具包,并从FileChooser检索文件。第一件事只能做一次;第二个应写成可重复使用。

最后你的循环

while(isReady == false){
    isReady = FileGetter.getIsReady();      
}

是非常糟糕的做法:它尽可能快地检查isReady标志。在某些(相当不寻常的)情况下,它甚至可以阻止FX Application线程运行任何资源。这应该阻止,直到文件准备好。

如何修复Main JavaFX Application

所以,只有你真的迫切需要这样做,我才会创建一个只负责启动FX工具包的类。类似的东西:

import java.util.concurrent.CountDownLatch;
import java.util.concurrent.atomic.AtomicBoolean;

import javafx.application.Application;
import javafx.application.Platform;
import javafx.stage.Stage;

public class FXStarter extends Application {

    private static final AtomicBoolean startRequested = new AtomicBoolean(false);
    private static final CountDownLatch latch = new CountDownLatch(1);

    @Override
    public void init() {
        Platform.setImplicitExit(false);
    }

    @Override
    public void start(Stage primaryStage) {
        latch.countDown();
    }

    /** Starts the FX toolkit, if not already started via this method,
     ** and blocks execution until it is running.
     **/
    public static void startFXIfNeeded() throws InterruptedException {
        if (! startRequested.getAndSet(true)) {
            new Thread(Application::launch).start();
        }
        latch.await();
    }
}

现在创建一个为您获取文件的类。这应该确保FX工具包正在运行,使用前一个类。此实现允许您从任何线程调用getFile()

import java.io.File;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;

import javafx.application.Platform;
import javafx.stage.FileChooser;


public class FileGetter {

    /**
     ** Retrieves a file from a JavaFX File chooser. This method can 
     ** be called from any thread, and will block until the user chooses
     ** a file.
     **/

    public File getFile() throws InterruptedException {

        FXStarter.startFXIfNeeded() ;

        if (Platform.isFxApplicationThread()) {
            return doGetFile();
        } else {
            FutureTask<File> task = new FutureTask<File>(this::doGetFile);
            Platform.runLater(task);
            try {
                return task.get();
            } catch (ExecutionException exc) {
                throw new RuntimeException(exc);
            }
        }
    }

    private File doGetFile() {
        File file = null ;
        FileChooser chooser = new FileChooser() ;

        while (file == null) {
            file = chooser.showOpenDialog(null) ;
        }

        return file ;
    }
}

最后你的Main只是

import java.io.File;

public class Main {

    public static void main(String[] args) throws InterruptedException {
        File file = new FileGetter().getFile();
        // proceed...
    }
}

同样,这非常复杂;我认为没有理由不简单地使用标准的FX应用程序生命周期,就像答案中的第一个代码块一样。