You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

长按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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.04.23 13:54:08