布尔值不会从 Object.getBoolean() 更新;
Boolean wont Update from Object.getBoolean();
差不多,我正在尝试编写一个让用户选择文件的简单程序。不幸的是,通过 Swing 的 JFileChooser 有点过时了,所以我正在尝试为此使用 JavaFX FileChooser。目标是 运行 FileGetter 作为一个线程,将文件数据传输到 Main Class,然后从那里继续。
主要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 Class:
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) .
非常感谢任何帮助、替代建议或解释为什么会发生这种情况!
在此代码中
while(isReady == false){
isReady = FileGetter.getIsReady();
}
没有任何东西可以将 FileGetter
的 isReady 状态更改为 true
java 内存模型不要求变量值在不同线程中相同,除非在特定条件下。
这里发生的事情是 FileGetter
线程正在更新自己的内存中的值,该内存只能从该线程访问,但是您的主线程看不到更新后的值,因为它只看到存储在它自己的内存中的变量版本与 FileGetter
线程之一不同。每个线程在内存中都有自己的字段副本,根据 java 规范,这完全没问题。
要解决此问题,您只需将 volatile
修饰符添加到 isReady
:
static volatile boolean isReady = false;
这确保更新后的值在您的主线程中可见。
此外,我建议减少您创建的 FileGetter
个实例的数量。在您的代码中创建了 3 个实例,但只使用了 1 个。
Thread t1 = new Thread(() -> Application.launch(FileGetter.class));
t1.start();
...
最简单的实现方式
为什么不遵循标准的 JavaFX 生命周期,而不是试图用手推车来驱动马?换句话说,让你的 Main
class 成为 Application
的子 class,在 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
class 与 JavaFX Application
class 分开(这实际上没有意义:一旦您决定使用 JavaFX FileChooser
,你已经决定你正在编写一个 JavaFX 应用程序,所以启动 class 应该是 Application
的子 class),然后它变得有点棘手.您的代码目前存在几个问题,其中一些问题已在其他答案中解决。如 Fabian 的回答所示,主要问题是您在不确保活动性的情况下从多个线程引用 FileGetter.isReady
。这正是 Josh Bloch 的 Effective Java(第 2 版第 66 项)中解决的问题。
您的代码的另一个问题是您将无法多次使用 FileGetter
(您不能多次调用 launch()
),这可能不是现在在您的代码中出现问题,但几乎可以肯定,随着开发的进行,此应用程序会在某个时刻出现。问题是您混合了两个问题:启动 FX 工具包和从 FileChooser
检索文件。第一件事只能做一次;第二个应该写成可重用的。
最后是你的循环
while(isReady == false){
isReady = FileGetter.getIsReady();
}
是非常糟糕的做法:它尽可能快地检查 isReady
标志。在某些(相当不寻常的)情况下,它甚至可以阻止 FX 应用程序线程拥有 运行 的任何资源。这应该只是阻塞,直到文件准备就绪。
如何在不制作 Main
JavaFX 的情况下进行修复 Application
因此,只有当您确实有迫切需要时,我才会首先创建一个 class,它只负责启动 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();
}
}
现在创建一个 class 来为您获取文件。这应该确保 FX 工具包是 运行ning,使用以前的 class。此实现允许您从任何线程调用 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 应用程序生命周期,就像答案中的第一个代码块一样。
差不多,我正在尝试编写一个让用户选择文件的简单程序。不幸的是,通过 Swing 的 JFileChooser 有点过时了,所以我正在尝试为此使用 JavaFX FileChooser。目标是 运行 FileGetter 作为一个线程,将文件数据传输到 Main Class,然后从那里继续。
主要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 Class:
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) .
非常感谢任何帮助、替代建议或解释为什么会发生这种情况!
在此代码中
while(isReady == false){
isReady = FileGetter.getIsReady();
}
没有任何东西可以将 FileGetter
的 isReady 状态更改为 true
java 内存模型不要求变量值在不同线程中相同,除非在特定条件下。
这里发生的事情是 FileGetter
线程正在更新自己的内存中的值,该内存只能从该线程访问,但是您的主线程看不到更新后的值,因为它只看到存储在它自己的内存中的变量版本与 FileGetter
线程之一不同。每个线程在内存中都有自己的字段副本,根据 java 规范,这完全没问题。
要解决此问题,您只需将 volatile
修饰符添加到 isReady
:
static volatile boolean isReady = false;
这确保更新后的值在您的主线程中可见。
此外,我建议减少您创建的 FileGetter
个实例的数量。在您的代码中创建了 3 个实例,但只使用了 1 个。
Thread t1 = new Thread(() -> Application.launch(FileGetter.class));
t1.start();
...
最简单的实现方式
为什么不遵循标准的 JavaFX 生命周期,而不是试图用手推车来驱动马?换句话说,让你的 Main
class 成为 Application
的子 class,在 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
class 与 JavaFX Application
class 分开(这实际上没有意义:一旦您决定使用 JavaFX FileChooser
,你已经决定你正在编写一个 JavaFX 应用程序,所以启动 class 应该是 Application
的子 class),然后它变得有点棘手.您的代码目前存在几个问题,其中一些问题已在其他答案中解决。如 Fabian 的回答所示,主要问题是您在不确保活动性的情况下从多个线程引用 FileGetter.isReady
。这正是 Josh Bloch 的 Effective Java(第 2 版第 66 项)中解决的问题。
您的代码的另一个问题是您将无法多次使用 FileGetter
(您不能多次调用 launch()
),这可能不是现在在您的代码中出现问题,但几乎可以肯定,随着开发的进行,此应用程序会在某个时刻出现。问题是您混合了两个问题:启动 FX 工具包和从 FileChooser
检索文件。第一件事只能做一次;第二个应该写成可重用的。
最后是你的循环
while(isReady == false){
isReady = FileGetter.getIsReady();
}
是非常糟糕的做法:它尽可能快地检查 isReady
标志。在某些(相当不寻常的)情况下,它甚至可以阻止 FX 应用程序线程拥有 运行 的任何资源。这应该只是阻塞,直到文件准备就绪。
如何在不制作 Main
JavaFX 的情况下进行修复 Application
因此,只有当您确实有迫切需要时,我才会首先创建一个 class,它只负责启动 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();
}
}
现在创建一个 class 来为您获取文件。这应该确保 FX 工具包是 运行ning,使用以前的 class。此实现允许您从任何线程调用 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 应用程序生命周期,就像答案中的第一个代码块一样。