Consider I have a sample JavaFX application which updates its UI with an image read from the application's JAR, and does so in a delayed manner (i.e. the image is painted after the UI is shown):
import javafx.application.Application;
import javafx.application.Platform;
import javafx.embed.swing.SwingFXUtils;
import javafx.scene.Node;
import javafx.scene.Scene;
import javafx.scene.control.Label;
import javafx.scene.image.ImageView;
import javafx.scene.layout.StackPane;
import javafx.stage.Stage;
import javax.imageio.ImageIO;
import java.awt.image.BufferedImage;
import java.io.BufferedInputStream;
import java.io.IOException;
import java.io.InputStream;
public final class SameThreadAsync extends Application {
@Override
public void start(final Stage primaryStage) {
final ImageView imageView = new ImageView();
imageView.setPreserveRatio(true);
imageView.setSmooth(true);
imageView.setFitWidth(300.0);
imageView.setFitHeight(300.0);
Platform.runLater(() -> {
final BufferedImage image = getIcon();
imageView.setImage(SwingFXUtils.toFXImage(image, null));
});
final Node label = new Label(null, imageView);
final StackPane root = new StackPane();
root.getChildren().add(label);
primaryStage.setScene(new Scene(root, 300.0, 300.0));
primaryStage.show();
}
private BufferedImage getIcon() {
System.out.println("Reading an image from thread " + Thread.currentThread().getName());
try (final InputStream in = new BufferedInputStream(getClass().getResourceAsStream("logo1.png"))) {
return ImageIO.read(in);
} catch (final IOException ioe) {
ioe.printStackTrace();
return new BufferedImage(0, 0, BufferedImage.TYPE_INT_ARGB);
}
}
public static void main(final String ... args) {
launch(args);
}
}
The above code works fine. Now, consider I want to load the image in a separate thread and process the result on the JavaFX Application Thread
, so I'll rewrite the code as follows:
import javafx.application.Application;
import javafx.application.Platform;
import javafx.embed.swing.SwingFXUtils;
import javafx.scene.Node;
import javafx.scene.Scene;
import javafx.scene.control.Label;
import javafx.scene.image.ImageView;
import javafx.scene.layout.StackPane;
import javafx.stage.Stage;
import javax.imageio.ImageIO;
import java.awt.image.BufferedImage;
import java.io.BufferedInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public final class SeparateThreadAsync extends Application {
private static final ExecutorService IO_EXECUTOR = Executors.newSingleThreadExecutor(r -> new Thread(r, "I/O Queue"));
@Override
public void start(final Stage primaryStage) {
final ImageView imageView = new ImageView();
imageView.setPreserveRatio(true);
imageView.setSmooth(true);
imageView.setFitWidth(300.0);
imageView.setFitHeight(300.0);
IO_EXECUTOR.submit(() -> {
final BufferedImage image = getIcon();
Platform.runLater(() -> imageView.setImage(SwingFXUtils.toFXImage(image, null)));
});
final Node label = new Label(null, imageView);
final StackPane root = new StackPane();
root.getChildren().add(label);
primaryStage.setScene(new Scene(root, 300.0, 300.0));
primaryStage.show();
}
private BufferedImage getIcon() {
System.out.println("Reading an image from thread " + Thread.currentThread().getName());
try (final InputStream in = new BufferedInputStream(getClass().getResourceAsStream("logo1.png"))) {
return ImageIO.read(in);
} catch (final IOException ioe) {
ioe.printStackTrace();
return new BufferedImage(0, 0, BufferedImage.TYPE_INT_ARGB);
}
}
public static void main(final String ... args) {
Runtime.getRuntime().addShutdownHook(new Thread(() -> {
IO_EXECUTOR.shutdown();
System.out.println("I/O Queue shut down.");
}));
launch(args);
}
}
The rewritten code works fine on both Linux and Windows but hangs on Mac OS X 10.14.5 (Mojave), Oracle JDK 1.8.0_192 and also JetBrains Runtime 1.8.0_202.
The partial thread dump is:
"I/O Queue" #19 prio=5 os_prio=31 tid=0x00007ffe3d85a000 nid=0x12007 runnable [0x0000700004822000]
java.lang.Thread.State: RUNNABLE
at java.lang.ClassLoader$NativeLibrary.load(Native Method)
at java.lang.ClassLoader.loadLibrary0(ClassLoader.java:1941)
- locked <0x000000076ab068a8> (a java.util.Vector)
- locked <0x000000076ab06900> (a java.util.Vector)
at java.lang.ClassLoader.loadLibrary(ClassLoader.java:1824)
at java.lang.Runtime.load0(Runtime.java:809)
- locked <0x000000076ab1dea8> (a java.lang.Runtime)
at java.lang.System.load(System.java:1086)
at java.lang.ClassLoader$NativeLibrary.load(Native Method)
at java.lang.ClassLoader.loadLibrary0(ClassLoader.java:1941)
- locked <0x000000076ab068a8> (a java.util.Vector)
- locked <0x000000076ab06900> (a java.util.Vector)
at java.lang.ClassLoader.loadLibrary(ClassLoader.java:1845)
at java.lang.Runtime.loadLibrary0(Runtime.java:870)
- locked <0x000000076ab1dea8> (a java.lang.Runtime)
at java.lang.System.loadLibrary(System.java:1122)
at java.awt.Toolkit$3.run(Toolkit.java:1636)
at java.awt.Toolkit$3.run(Toolkit.java:1634)
at java.security.AccessController.doPrivileged(Native Method)
at java.awt.Toolkit.loadLibraries(Toolkit.java:1633)
at java.awt.Toolkit.<clinit>(Toolkit.java:1670)
at sun.awt.AppContext$2.run(AppContext.java:277)
at sun.awt.AppContext$2.run(AppContext.java:266)
at java.security.AccessController.doPrivileged(Native Method)
at sun.awt.AppContext.initMainAppContext(AppContext.java:266)
at sun.awt.AppContext.access$400(AppContext.java:135)
at sun.awt.AppContext$3.run(AppContext.java:321)
- locked <0x000000076c238c00> (a sun.awt.AppContext$GetAppContextLock)
at sun.awt.AppContext$3.run(AppContext.java:304)
at java.security.AccessController.doPrivileged(Native Method)
at sun.awt.AppContext.getAppContext(AppContext.java:303)
at javax.imageio.spi.IIORegistry.getDefaultInstance(IIORegistry.java:154)
at javax.imageio.ImageIO.<clinit>(ImageIO.java:66)
at com.example.SeparateThreadAsync.getIcon(SeparateThreadAsync.java:49)
at com.example.SeparateThreadAsync.lambda$start$2(SeparateThreadAsync.java:33)
at com.example.SeparateThreadAsync$$Lambda$61/1820086024.run(Unknown Source)
at java.util.concurrent.Executors$RunnableAdapter.call(Executors.java:511)
at java.util.concurrent.FutureTask.run(FutureTask.java:266)
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1149)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624)
at java.lang.Thread.run(Thread.java:748)
"JavaFX Application Thread" #15 prio=5 os_prio=31 tid=0x00007ffe3b0e4000 nid=0x307 waiting for monitor entry [0x00007ffee75f1000]
java.lang.Thread.State: BLOCKED (on object monitor)
at java.lang.Runtime.load0(Runtime.java:801)
- waiting to lock <0x000000076ab1dea8> (a java.lang.Runtime)
at java.lang.System.load(System.java:1086)
at com.sun.glass.utils.NativeLibLoader.loadLibraryFullPath(NativeLibLoader.java:201)
at com.sun.glass.utils.NativeLibLoader.loadLibraryInternal(NativeLibLoader.java:94)
at com.sun.glass.utils.NativeLibLoader.loadLibrary(NativeLibLoader.java:39)
- locked <0x000000076b4185c0> (a java.lang.Class for com.sun.glass.utils.NativeLibLoader)
at com.sun.javafx.font.PrismFontFactory.lambda$static$244(PrismFontFactory.java:100)
at com.sun.javafx.font.PrismFontFactory$$Lambda$70/1988937384.run(Unknown Source)
at java.security.AccessController.doPrivileged(Native Method)
at com.sun.javafx.font.PrismFontFactory.<clinit>(PrismFontFactory.java:98)
at com.sun.javafx.text.PrismTextLayout.<clinit>(PrismTextLayout.java:67)
at com.sun.javafx.text.PrismTextLayoutFactory.<clinit>(PrismTextLayoutFactory.java:33)
at com.sun.javafx.tk.quantum.QuantumToolkit.getTextLayoutFactory(QuantumToolkit.java:1086)
at com.sun.javafx.scene.control.skin.Utils.<clinit>(Utils.java:90)
at java.lang.Class.forName0(Native Method)
at java.lang.Class.forName(Class.java:348)
at com.sun.javafx.css.StyleManager.getURL(StyleManager.java:863)
at com.sun.javafx.css.StyleManager.loadStylesheetUnPrivileged(StyleManager.java:1075)
- locked <0x000000076c3bc948> (a java.lang.Object)
at com.sun.javafx.css.StyleManager.loadStylesheet(StyleManager.java:935)
at com.sun.javafx.css.StyleManager._setDefaultUserAgentStylesheet(StyleManager.java:1395)
- locked <0x000000076c3bc948> (a java.lang.Object)
at com.sun.javafx.css.StyleManager.setUserAgentStylesheets(StyleManager.java:1227)
- locked <0x000000076c3bc948> (a java.lang.Object)
at com.sun.javafx.application.PlatformImpl.lambda$_setPlatformUserAgentStylesheet$181(PlatformImpl.java:698)
at com.sun.javafx.application.PlatformImpl$$Lambda$65/566730701.run(Unknown Source)
at java.security.AccessController.doPrivileged(Native Method)
at com.sun.javafx.application.PlatformImpl._setPlatformUserAgentStylesheet(PlatformImpl.java:697)
at com.sun.javafx.application.PlatformImpl.setPlatformUserAgentStylesheet(PlatformImpl.java:548)
at com.sun.javafx.application.PlatformImpl.setDefaultPlatformUserAgentStylesheet(PlatformImpl.java:512)
at javafx.scene.control.Control.<clinit>(Control.java:87)
at com.example.SeparateThreadAsync.start(SeparateThreadAsync.java:37)
at com.sun.javafx.application.LauncherImpl.lambda$launchApplication1$161(LauncherImpl.java:863)
at com.sun.javafx.application.LauncherImpl$$Lambda$56/1315653396.run(Unknown Source)
at com.sun.javafx.application.PlatformImpl.lambda$runAndWait$174(PlatformImpl.java:326)
at com.sun.javafx.application.PlatformImpl$$Lambda$47/1212899836.run(Unknown Source)
at com.sun.javafx.application.PlatformImpl.lambda$null$172(PlatformImpl.java:295)
at com.sun.javafx.application.PlatformImpl$$Lambda$49/1963951195.run(Unknown Source)
at java.security.AccessController.doPrivileged(Native Method)
at com.sun.javafx.application.PlatformImpl.lambda$runLater$173(PlatformImpl.java:294)
at com.sun.javafx.application.PlatformImpl$$Lambda$48/1289696681.run(Unknown Source)
at com.sun.glass.ui.InvokeLaterDispatcher$Future.run(InvokeLaterDispatcher.java:95)
so it seems like some sort of a race condition. Interestingly, a similar JFC/Swing application doesn't hang at all.
I first encountered this behaviour while playing with coroutines in Kotlin. For example, this code doesn't hang while this one does.
Questions:
I'm writing an application that needs to lazy-load image thumbnails on a separate thread. ImageIO.read() was hanging, too. But someone suggested doing
new BufferedImage(1, 1, BufferedImage.TYPE_INT_RGB);
in the main application thread before starting other threads, as this apparently causes AWT to run some initialization code the first time which has to be done in the main thread. That solved the ImageIO.read problem.
But it was also hanging when calling
SwingFXUtils.toFXImage(bufferedImage, null);
so I moved that inside my "Platform.runLater(...)" call so that it would be done on the JavaFX thread, and that worked, though it isn't ideal, since I would like that conversion to happen on the separate thread so the UI will be more responsive. But that conversion happens in memory, so I can live with it.