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
相关产品推荐
相关产品推荐

