如何让ScheduledExecutorService在用户退出程序时正确终止?
问题解决思路
一、修复Alert弹窗一闪而过的问题
你当前使用的alert.show()是异步显示弹窗,不会阻塞后续代码执行,导致程序直接走到super.stop(),弹窗还没等用户操作就被关闭了。把它换成showAndWait()就能阻塞直到用户做出选择,同时还要处理用户选择“NO”时的应用关闭拦截:
@Override public void stop() throws Exception { Alert alert = new Alert(Alert.AlertType.CONFIRMATION, "Exit ?", ButtonType.YES, ButtonType.NO); // 用showAndWait()替代show(),阻塞等待用户选择 Optional<ButtonType> result = alert.showAndWait(); if (result.isPresent() && result.get() == ButtonType.YES) { BlueSlime.scheduler.shutdown(); // 可选:等待线程池终止,避免残留后台线程 if (!BlueSlime.scheduler.awaitTermination(3, TimeUnit.SECONDS)) { BlueSlime.scheduler.shutdownNow(); } super.stop(); } else { System.out.println("No"); // 阻止应用默认关闭流程 Platform.setImplicitExit(false); } }
二、优化ScheduledExecutorService的管理方式
用静态变量直接引用BlueSlime.scheduler会造成类之间强耦合,不利于维护,推荐两种更优的方案:
方式1:Main类持有线程池引用,传递给BlueSlime
把线程池的创建移到Main类中,通过构造方法或setter传递给BlueSlime,解耦类之间的依赖:
public class Main extends Application { private ScheduledExecutorService scheduler; @Override public void start(Stage primaryStage) { scheduler = Executors.newScheduledThreadPool(1); BlueSlime blueSlime = new BlueSlime(scheduler); // ... 其他初始化逻辑 } @Override public void stop() throws Exception { Alert alert = new Alert(Alert.AlertType.CONFIRMATION, "Exit ?", ButtonType.YES, ButtonType.NO); Optional<ButtonType> result = alert.showAndWait(); if (result.isPresent() && result.get() == ButtonType.YES) { scheduler.shutdown(); if (!scheduler.awaitTermination(3, TimeUnit.SECONDS)) { scheduler.shutdownNow(); } super.stop(); } else { System.out.println("No"); Platform.setImplicitExit(false); } } } // BlueSlime类 public class BlueSlime { private ScheduledExecutorService scheduler; private Runnable shootRunnable; public BlueSlime(ScheduledExecutorService scheduler) { this.scheduler = scheduler; this.shootRunnable = () -> System.out.println("SHOOT"); scheduler.scheduleAtFixedRate(shootRunnable, 2, 2, TimeUnit.SECONDS); } // Enemy销毁时调用 public void destroy() { scheduler.shutdown(); } }
方式2:使用JavaFX内置的ScheduledService(JavaFX 11+)
如果你的项目使用JavaFX 11及以上版本,推荐用ScheduledService替代ScheduledExecutorService,它天然和JavaFX生命周期绑定,无需手动管理线程池:
// 自定义定时任务服务 public class ShootService extends ScheduledService<Void> { @Override protected Task<Void> createTask() { return new Task<>() { @Override protected Void call() { System.out.println("SHOOT"); return null; } }; } } // BlueSlime类中使用 public class BlueSlime { private ShootService shootService; public BlueSlime() { shootService = new ShootService(); shootService.setPeriod(Duration.seconds(2)); shootService.start(); } public void destroy() { shootService.cancel(); } } // Main类中关闭 @Override public void stop() throws Exception { Alert alert = new Alert(Alert.AlertType.CONFIRMATION, "Exit ?", ButtonType.YES, ButtonType.NO); Optional<ButtonType> result = alert.showAndWait(); if (result.isPresent() && result.get() == ButtonType.YES) { // 假设持有BlueSlime实例引用 blueSlime.shootService.cancel(); super.stop(); } else { System.out.println("No"); Platform.setImplicitExit(false); } }
额外注意事项
shutdown()会拒绝新任务,但会完成已提交的任务;如果需要立即终止所有任务,可调用shutdownNow(),但可能导致任务中断,需根据业务场景选择。awaitTermination()设置超时时间,等待线程池完全终止,避免程序退出后残留后台线程。
内容的提问来源于stack exchange,提问作者Ryan Sangha
相关产品推荐
相关产品推荐

