【问题标题】:Boolean wont Update from Object.getBoolean();布尔值不会从 Object.getBoolean() 更新;
【发布时间】:2016-08-18 02:56:15
【问题描述】:

差不多,我正在尝试编写一个简单的程序,让用户选择一个文件。不幸的是,通过 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)。

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

【问题讨论】:

  • 在 FileGetter 中创建您的 isReady volatile
  • @Codebender 成功了!谢谢!只是好奇作为旁注,由于某种原因,当我包含 System.out.println(isReady); 时它起作用了。在while循环中。有人知道这是什么原因吗?
  • FileGetter#start 中的 while 循环是为了确保用户确实选择了一个文件,而 Main 类中的 while 循环是为了确保在选择文件之前代码不会继续。但是是的,我可以在两者中使用相同的条件语句,但是我相信这是同一个概念。
  • 感谢大家的帮助!
  • 哦,好的。我明白你在说什么!谢谢大家的帮助!

标签: java javafx jfilechooser filechooser


【解决方案1】:

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

这里发生的事情是FileGetter 线程正在更新自己的内存中的值,该值只能从此线程访问,但是您的主线程看不到更新的值,因为它只看到了变量存储在它自己的内存中,不同于FileGetter 线程之一。每个线程在内存中都有自己的字段副本,根据 java 规范,这完全没问题。

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

static volatile boolean isReady = false;

确保更新后的值在您的主线程中可见。

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

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

【讨论】:

    【解决方案2】:

    最简单的实现方式

    与其试图用马车赶马,为什么不遵循标准的 JavaFX 生命周期?换句话说,让您的Main 类成为Application 的子类,在start() 方法中获取文件,然后继续(在后台线程中)处理应用程序的其余部分?

    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 应用程序线程拥有任何资源来运行。这应该只是阻塞,直到文件准备好。


    如何在不使 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 应用程序生命周期,就像答案中的第一个代码块一样。

    【讨论】:

    • 在我的代码上使用仍然具有main 类的代码有什么优势(如果我更新它以将文件数据存储在我的main 类中并将布尔和文件类型更新为 volatile )(不是试图诋毁您的代码或其他任何东西,只是对优势感到好奇)?
    • 不确定您所说的“更新它以将文件数据存储在我的 Main 类中。您将如何启动 FX 工具包?
    • 只需在我的main 类中创建一个文件变量并将用户的文件选择存储在其中(我认为您之前引用过我无法通过FileGetter.getFile() 继续引用该文件时线程停止了,所以我将它存储在 File 变量中?)。
    • 但是如果没有busy-while循环,你怎么能等到用户选择了一些东西呢?我想我不明白你在问什么;也许显示一些代码。我的意思是你以后不能再向用户索要另一个文件,因为你不能在同一个应用程序中多次调用launch()
    • 好的,我回去重读了您所说的一些内容,现在变得更有意义了,谢谢。另外,我为我对所有这些东西的糟糕措辞道歉。我想说的只是在我的主类中创建一个 File 变量并将其设置为用户选择的文件。一个简单的File file = FileGetter.getFile(),而不是在代码的其余部分中将文件称为FileGetter.getFile()。感谢您的所有帮助。
    【解决方案3】:

    在这段代码中

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

    没有什么可以将FileGetter的isReady状态更改为true

    【讨论】:

    • 等一下,当fileFileGetter#start 中变为非空时会怎样?我可能只是错过了一些东西。
    • 是的,我试过了,但由于某种原因它不起作用......当用户从 FileChooser 中选择文件时,isReady 应该设置为 true。
    • 这也是一个可怕的忙等待。
    • @JonnyHenly 这一切都发生在不同的线程上。无法保证当前线程何时或什至 if 会在不同线程上看到对 isReady 所做的更改。请参阅 fabian 的回答和Effective Java, 2nd edition, Item 66
    猜你喜欢
    • 1970-01-01
    • 2016-04-22
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2016-12-19
    相关资源
    最近更新 更多