差不多,我试图编写一个让用户选择文件的简单程序。不幸的是,通过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)
非常感谢任何帮助,替代建议或解释为什么会发生这种情况!
答案 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应用程序生命周期,就像答案中的第一个代码块一样。