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

Java Swing时钟调用FreeTTS语音播报阻塞计时线程如何解决

问题根因

你使用的Swing Timer 绑定的动作回调默认运行在*Swing事件调度线程(EDT)*上,该线程负责所有界面绘制、事件响应、计时触发逻辑。你在回调中直接调用的voice.speak()是同步阻塞方法,执行时会全程占用EDT线程,导致计时、界面刷新逻辑全部被挂起,直到语音播放完毕才会恢复,表现为时钟暂停。

修复方案

核心原则是所有阻塞操作都不能放在EDT线程执行,同时优化冗余的资源初始化逻辑:

  • 提前初始化语音引擎,不要每次触发预警都重复加载语音资源
  • 将语音播放逻辑放到独立后台线程运行,和计时、UI逻辑完全并行
  • 增加重复触发拦截,避免同一分钟多次播报、或者上一段语音没结束就重复触发
  • 后台线程不直接读取UI组件内容,避免Swing线程安全问题

调整后代码

首先在类成员位置定义公共变量,在程序初始化阶段完成语音引擎加载:

// 类成员变量
private Voice voice;
private volatile boolean isVoicePlaying = false;
private int lastAnnouncedMinute = -1;

// 时钟启动前调用一次即可,不要重复执行
private void initVoice() {
    System.setProperty("freetts.voices", "com.sun.speech.freetts.en.us.cmu_us_kal.KevinVoiceDirectory");
    VoiceManager vm = VoiceManager.getInstance();
    voice = vm.getVoice(SayWhat);
    voice.setStyle("robotic");
    voice.setVolume(200);
    voice.allocate();
}

改造语音播报方法,用独立线程跑阻塞的播放逻辑:

private void warningVoice(int currentMinute) {
    // 重复触发直接跳过
    if (isVoicePlaying || lastAnnouncedMinute == currentMinute) {
        return;
    }
    lastAnnouncedMinute = currentMinute;
    // 开后台线程执行语音播放,不占用EDT
    new Thread(() -> {
        isVoicePlaying = true;
        try {
            String writing = "You Have " + currentMinute + " Minutes Left";
            voice.speak(writing);
        } catch (Exception ex) {
            ex.printStackTrace();
        } finally {
            isVoicePlaying = false;
        }
    }).start();
}

替换原时钟逻辑里的问题调用行:

if (minute <= 10) {
    // 直接传当前分钟值,不要在后台线程读UI组件
    warningVoice(minute);
}

注意:isVoicePlaying 变量要加volatile修饰,保证多线程之间的状态可见性,避免出现状态判断不一致的问题。

内容的提问来源于stack exchange,提问作者MK.Whridoy

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.09.01 00:04:09