长按HBox触发的线程run方法多次执行异常求助
长按HBox触发的线程run方法多次执行异常求助
看起来你遇到的问题是多次触发了长按逻辑——明明只按了一次,却有多个线程在800ms后执行了editMode = true的代码,导致handleEditMode被多次调用,重复添加操作按钮对吧?
我先帮你分析下原有代码的问题根源:
问题出在哪?
每次按下都创建新线程,没有去重/取消机制
你的代码里,每触发一次MOUSE_PRESSED事件,就会新建一个线程并让它sleep 800ms。如果因为误触、事件冒泡(比如HBox里有子控件,导致事件被多次分发)、或者系统的重复事件触发,就会同时存在多个线程在等待。当这些线程陆续醒来时,只要mousePressed还是true(比如你一直按着没松手),它们都会执行editMode = true的逻辑。共享变量的线程安全问题
mousePressed和editMode都是普通的boolean变量,没有做线程同步或可见性保障。多个线程读写这些变量时,可能出现线程缓存导致的可见性问题——比如某个线程已经把editMode设为true,但其他线程还看不到这个变更,依然会执行进入if语句的逻辑。没有提前拦截重复操作
当已经进入编辑模式后,新的MOUSE_PRESSED事件依然会创建线程,这也会导致不必要的重复执行。
怎么解决?
我给你调整一下代码,核心思路是:用定时任务调度器替代手动创建线程,并通过原子变量保证线程安全,同时拦截重复操作:
第一步:替换成员变量为线程安全类型,添加调度器
// 用AtomicBoolean保证线程安全的读写和可见性 private AtomicBoolean mousePressed = new AtomicBoolean(false); private AtomicBoolean editMode = new AtomicBoolean(false); // 用来调度长按任务,方便取消之前的未执行任务 private ScheduledFuture<?> longPressTask; private ScheduledExecutorService scheduler = Executors.newSingleThreadScheduledExecutor();
第二步:修改MOUSE_PRESSED事件处理
hBox.addEventHandler(MouseEvent.MOUSE_PRESSED, e -> { // 已经在编辑模式,直接跳过新的按下事件 if (editMode.get()) { return; } mousePressed.set(true); pillReminder.setSelected(true); hBox.setBackground(new Background(new BackgroundFill(Color.CYAN, CornerRadii.EMPTY, Insets.EMPTY))); // 取消之前还没执行的长按任务(如果有的话) if (longPressTask != null && !longPressTask.isCancelled()) { longPressTask.cancel(false); } // 调度一个延迟800ms的任务,替代原来的手动线程 longPressTask = scheduler.schedule(() -> { // 只有当还按着,且还没进入编辑模式时,才执行逻辑 if (mousePressed.get() && editMode.compareAndSet(false, true)) { Platform.runLater(this::handleEditMode); } }, 800, TimeUnit.MILLISECONDS); // 阻止事件冒泡,避免被其他监听器重复处理 e.consume(); });
第三步:修改MOUSE_RELEASED事件处理
hBox.addEventHandler(MouseEvent.MOUSE_RELEASED, mouseEvent -> { mousePressed.set(false); // 松手后立即取消未执行的长按任务 if (longPressTask != null && !longPressTask.isDone()) { longPressTask.cancel(false); } if (!editMode.get()) { hBox.setBackground(new Background(new BackgroundFill(Color.LIGHTCYAN, CornerRadii.EMPTY, Insets.EMPTY))); if (hBox.getBoundsInLocal().contains(new Point2D(mouseEvent.getX(), mouseEvent.getY()))) { handleReminderHubReleased(pillReminder); } } });
第四步:记得关闭调度器(防止线程泄漏)
比如在窗口关闭的时候添加:
// 假设你的窗口是stage,可以在close事件里处理 stage.setOnCloseRequest(event -> { scheduler.shutdown(); });
关键改进点说明
- 用ScheduledExecutorService替代手动线程:可以轻松取消之前未执行的任务,确保同一时间只有一个长按任务在等待,从根源上避免多个线程同时执行的问题。
- AtomicBoolean的compareAndSet方法:确保只有第一个线程能成功将
editMode从false改为true,后续线程会因为条件不满足而跳过执行逻辑。 - 提前拦截编辑模式下的新事件:进入编辑模式后,不再处理新的按下事件,避免无效操作。
- 事件消费:通过
e.consume()阻止事件冒泡,避免被其他控件的监听器重复触发。
这样应该就能解决你遇到的随机多次执行的问题了!
备注:内容来源于stack exchange,提问作者MilckShake Beans
相关产品推荐
相关产品推荐

