JavaFX多线程应用问题:定时控制挖掘机对象移动
解决JavaFX多线程挖掘机定时动作问题
我来帮你梳理并解决这个定时控制挖掘机动作的问题。首先明确你的核心需求:
- 5台挖掘机在00:00-00:04:59和01:05-24:00时段停留在窗口中心
- 00:05:00-00:34:59执行向上移动并持续半小时
- 00:35:00-01:04:59执行向下移动并持续半小时
先看你现有代码里的几个关键问题:
- Time线程每次调用
setTime都会新建Excavators线程,这会导致线程泛滥,完全没必要 - Excavators类的run方法不完整,没有实现任何动作逻辑
- 每个挖掘机单独写move方法,代码冗余严重
- 时间推进的sleep是10ms,相当于时间流速是真实的100倍(如果是模拟加速没问题,我默认按真实时间每秒推进,你可以按需调整)
下面是修正后的完整解决方案:
第一步:重构Excavators类,统一管理所有挖掘机
我们把5个ImageView封装到Excavators类里,统一处理它们的位置逻辑,避免重复代码:
import javafx.application.Platform; import javafx.scene.image.Image; import javafx.scene.image.ImageView; import javafx.scene.layout.StackPane; public class Excavators { private final ImageView[] excavators; private final StackPane root; // 中心位置坐标(StackPane中TranslateX/Y为0就是中心) private static final double CENTER_X = 0; private static final double CENTER_Y = 0; // 移动步长(可根据需求调整移动速度) private static final double MOVE_STEP = 1; // 移动方向标记:1=向上,-1=向下,0=停留 private int moveDirection = 0; public Excavators(StackPane root, Image excavatorImage) { this.root = root; this.excavators = new ImageView[5]; // 初始化5台挖掘机 for (int i = 0; i < excavators.length; i++) { excavators[i] = new ImageView(excavatorImage); // 初始位置设为中心 resetToCenter(); root.getChildren().add(excavators[i]); } // 启动单独的移动线程,控制移动帧率 startMovementThread(); } // 让所有挖掘机回到中心位置并停留 public void stay() { moveDirection = 0; Platform.runLater(this::resetToCenter); } // 触发向上移动 public void moveUp() { moveDirection = 1; } // 触发向下移动 public void moveDown() { moveDirection = -1; } // 重置所有挖掘机到中心位置 private void resetToCenter() { for (ImageView excavator : excavators) { excavator.setTranslateX(CENTER_X); excavator.setTranslateY(CENTER_Y); } } // 处理持续移动逻辑的线程 private void startMovementThread() { new Thread(() -> { while (!Thread.currentThread().isInterrupted()) { if (moveDirection != 0) { Platform.runLater(() -> { for (ImageView excavator : excavators) { // 向上移动时Y坐标减小,向下时增大 excavator.setTranslateY(excavator.getTranslateY() - moveDirection * MOVE_STEP); } }); } try { Thread.sleep(40); // 控制移动帧率,和你原代码保持一致 } catch (InterruptedException e) { Thread.currentThread().interrupt(); break; } } }).start(); } }
第二步:修正Time线程,准确控制时间并触发动作
现在Time线程只负责推进时间,并根据当前时间调用Excavators对应的方法,不再重复创建线程:
public class Time extends Thread { private int hour = 0; private int minute = 0; private int second = 0; private final Main main; private final Excavators excavators; public Time(Main main, Excavators excavators) { this.main = main; this.excavators = excavators; } @Override public void run() { while (!Thread.currentThread().isInterrupted()) { try { // 更新时间逻辑 second++; if (second == 60) { second = 0; minute++; } if (minute == 60) { minute = 0; hour++; } if (hour == 24) { hour = 0; minute = 0; second = 0; } // 根据当前时间判断并触发挖掘机动作 checkAndTriggerExcavatorAction(); // 更新UI上的时间显示 main.updateTime(hour, minute, second); System.out.printf("%02d:%02d:%02d%n", hour, minute, second); // 真实时间每秒推进一次,想加速就改小这个值(比如10ms=100倍速) Thread.sleep(1000); } catch (InterruptedException e) { Thread.currentThread().interrupt(); } } } // 判断当前时间对应的挖掘机动作 private void checkAndTriggerExcavatorAction() { boolean isStayPeriod1 = (hour == 0 && minute < 5); boolean isMoveUpPeriod = (hour == 0 && minute >=5 && minute <35); boolean isMoveDownPeriod = (hour ==0 && minute >=35) || (hour ==1 && minute <5); boolean isStayPeriod2 = (hour >1) || (hour ==1 && minute >=5); if (isStayPeriod1 || isStayPeriod2) { excavators.stay(); } else if (isMoveUpPeriod) { excavators.moveUp(); } else if (isMoveDownPeriod) { excavators.moveDown(); } } }
第三步:简化Main类,整合核心逻辑
现在Main类不需要单独管理每个ImageView,只需要初始化Excavators和Time线程即可:
import javafx.application.Application; import javafx.application.Platform; import javafx.scene.Scene; import javafx.scene.image.Image; import javafx.scene.layout.StackPane; import javafx.scene.text.Text; import javafx.stage.Stage; import javafx.stage.WindowEvent; public class Main extends Application { private StackPane root = new StackPane(); private Text timeDisplay = new Text(); private Excavators excavators; @Override public void start(Stage primaryStage) throws Exception { root.setStyle("-fx-background-color: #00FF00"); primaryStage.setTitle("Excavator Scheduler"); primaryStage.setScene(new Scene(root, 1000, 800)); // 初始化挖掘机 Image excavatorImage = new Image("resources/excavator.png"); excavators = new Excavators(root, excavatorImage); // 初始化时间显示控件 timeDisplay.setTranslateX(-450); timeDisplay.setTranslateY(-380); root.getChildren().add(timeDisplay); // 启动时间线程 Time timeThread = new Time(this, excavators); timeThread.start(); // 关闭窗口时终止线程,避免资源泄漏 primaryStage.setOnCloseRequest((WindowEvent t) -> { timeThread.interrupt(); Platform.exit(); System.exit(0); }); primaryStage.show(); } public static void main(String[] args) { launch(args); } // 更新UI上的时间显示(确保在JavaFX主线程执行) public void updateTime(int hour, int minute, int second) { String timeStr = String.format("%02d:%02d:%02d", hour, minute, second); Platform.runLater(() -> timeDisplay.setText(timeStr)); } }
关键改进点说明
- 代码复用:把5台挖掘机的管理统一到Excavators类,消除了重复的moveExcavator1~5方法
- 线程安全:所有UI更新都通过
Platform.runLater确保在JavaFX主线程执行,避免线程安全问题 - 逻辑清晰:时间判断和动作触发分离,Time只负责时间推进,Excavators负责动作执行
- 避免线程泛滥:只创建一个Time线程和一个移动线程,不再重复创建Excavators线程
- 边界处理:准确覆盖所有时间区间,包括01:00-01:04属于向下移动时段,01:05之后回到停留状态
内容的提问来源于stack exchange,提问作者Marcin Frąckiewicz
相关产品推荐
相关产品推荐

