Swing单次按键触发actionPerformed被双线程执行的原因及解决方法
问题产生原因
- 核心触发点是
GameLoop类processGameLoop方法的异常处理逻辑:当你按下m键时,EDT(AWT事件调度线程)会触发按键绑定的Action,第一次调用showMenu();showMenu()内部调用GameController.stop(),给运行中的游戏线程发中断信号,游戏线程正在sleep会抛出InterruptedException,而你在catch块里又主动调用了一次showMenu(),这就导致两个线程各调用了一次showMenu()。 - 辅助原因:
GameLoop的status字段没有加volatile修饰,多线程场景下修改的状态不能保证立即对其他线程可见,可能导致状态判断失效。GameController.stop()方法没有做幂等判断,不管游戏是否已经停止,都会执行重置逻辑,重复调用就会重复执行重置。- 游戏线程内直接调用操作Swing组件的
showMenu(),违反Swing单线程规则,也会带来额外的线程安全风险。
解决方法
- 首先删除
GameLoop类InterruptedExceptioncatch块中的GUIHandler.getInstance().showMenu()调用,主动中断游戏线程属于预期内的操作,不需要额外触发返回菜单逻辑。 - 给
GUIHandler增加是否处于菜单页的状态标记,showMenu()入口处先判断如果已经在菜单页就直接返回,实现方法幂等:
// 在GUIHandler类增加成员变量 private boolean isInMenu = true; public void startGame() { isInMenu = false; // 原有逻辑不变 } public void showMenu() { if (isInMenu) return; isInMenu = true; // 原有逻辑不变 }
- 给
GameLoop的status字段增加volatile修饰,保证多线程下状态的可见性:
protected volatile String status;
- 所有操作Swing组件的逻辑确保在EDT线程执行,比如
showMenu()里的面板切换逻辑可以套一层SwingUtilities.invokeLater,避免非EDT线程操作UI。 - 优化
GamePanel.reset()方法,去掉里面的initInput()调用,输入绑定只需要在构造函数执行一次即可,不需要每次重置面板重复绑定。
内容的提问来源于stack exchange,提问作者CcmU
相关产品推荐
相关产品推荐

