Java Swing回合制游戏使用wait()/notify()异常问题求助
问题根因
程序运行到notify()就中断、界面卡死的问题由三个核心错误共同导致,本质是对Swing运行规则、Java并发机制的理解存在偏差:
- 违反Swing单线程模型:Swing所有界面渲染、事件响应都运行在唯一的事件调度线程(EDT)上,你在
Okno.initialize()末尾写的while(true)无限循环直接跑在EDT上,完全占死线程资源,按钮点击事件根本没机会被派发处理。 wait()/notify()用法完全错误:这两个是Object类提供的线程间通信方法,必须在持有当前对象锁的同步代码块中才能调用,裸调用会直接抛出IllegalMonitorStateException,这就是程序跑到notify()就中断的直接原因。就算补了同步块,在EDT上调wait()会直接把整个界面线程挂起,界面会完全卡死无响应。- 回合逻辑实现思路和Swing模型冲突:Swing是事件驱动框架,用户点击按钮会自动触发回调事件,根本不需要写死循环轮询、用wait阻塞等待用户操作,这种写法天生和Swing的调度逻辑不兼容。另外你只靠
aktywny标记判断点击是否生效,没有实际禁用非当前回合的按钮,交互逻辑也存在漏洞。
修复方案
保留原有的类拆分结构,去掉错误的并发等待逻辑,改用Swing原生的事件驱动机制实现回合轮转:
- 删除堵死EDT的无限轮询循环
- 移除错误使用的
wait()/notify(),不需要额外引入多线程控制 - 给按钮增加实际的启用/禁用控制,非当前回合的按钮直接置灰不可点
- 点击按钮触发完业务逻辑后,自动切换当前激活的玩家方
修复后完整代码
启动类 OknoTester
package grafika; import javax.swing.SwingUtilities; public class OknoTester { public static void main(String[] args) { SwingUtilities.invokeLater(() -> new Okno()); } }
主窗口类 Okno
package grafika; import java.awt.Dimension; import javax.swing.JFrame; import javax.swing.JSplitPane; public class Okno { private JFrame frame; // 标记当前回合:true=左侧玩家行动,false=右侧玩家行动 private boolean isLeftTurn = true; private Komputer left; private Komputer right; public Okno() { initialize(); } private void initialize() { // 窗口基础配置 frame = new JFrame(); frame.setTitle("Gra w statki"); frame.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE); frame.setSize(new Dimension(600,100)); frame.setLocationRelativeTo(null); frame.setResizable(false); // 水平分割窗口 JSplitPane splitPaneH = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT); frame.getContentPane().add(splitPaneH); splitPaneH.setDividerSize(0); // 初始化双方操作面板 left = new Komputer(); right = new Komputer(); splitPaneH.setLeftComponent(left.Pokaz()); splitPaneH.setRightComponent(right.Pokaz()); splitPaneH.setResizeWeight(0.5); splitPaneH.setEnabled(false); // 给双方绑定回合结束回调 left.setTurnCompleteCallback(() -> switchTurn()); right.setTurnCompleteCallback(() -> switchTurn()); // 设置初始回合:左侧玩家先行动 updateTurnState(); frame.setVisible(true); } // 切换回合 private void switchTurn() { isLeftTurn = !isLeftTurn; updateTurnState(); } // 更新双方按钮的激活状态 private void updateTurnState() { left.CzytajRuch(isLeftTurn); right.CzytajRuch(!isLeftTurn); } }
操作面板类 Komputer
package grafika; import java.awt.Color; import java.awt.GridLayout; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import javax.swing.JButton; import javax.swing.JPanel; public class Komputer implements ActionListener{ private JButton b1 = new JButton("1"); private JButton b2 = new JButton("2"); private JPanel panel = new JPanel(); private Boolean aktywny = false; // 回合完成回调,通知主窗口切换玩家 private Runnable turnCompleteCallback; // 供主窗口设置回合结束回调 public void setTurnCompleteCallback(Runnable callback) { this.turnCompleteCallback = callback; } public void CzytajRuch(boolean isActive) { this.aktywny = isActive; // 非当前回合直接禁用按钮,避免误触 b1.setEnabled(isActive); b2.setEnabled(isActive); } public JPanel Pokaz() { return panel; } public Komputer() { GridLayout gl = new GridLayout(1,2); panel.setLayout(gl); panel.add(b1); b1.addActionListener(this); b2.addActionListener(this); panel.add(b2); // 初始状态所有按钮禁用 b1.setEnabled(false); b2.setEnabled(false); } public void actionPerformed(ActionEvent ae) { if(aktywny == true) { JButton pb = (JButton) ae.getSource(); // 点击按钮随机切换颜色,保留原有的验证逻辑 if (pb == b1) b1.setBackground(new Color((int)(Math.random() * 0x1000000))); if (pb == b2) b2.setBackground(new Color((int)(Math.random() * 0x1000000))); aktywny = false; b1.setEnabled(false); b2.setEnabled(false); // 通知主窗口当前回合结束,切换玩家 if(turnCompleteCallback != null) { turnCompleteCallback.run(); } } } }
注意事项
写Swing程序一定要牢记:永远不要在EDT上执行长耗时、阻塞类操作,轮询等待、网络请求、文件IO这类逻辑要么开独立后台线程处理,要么改用事件驱动的写法,等用户操作或者系统通知触发对应回调再执行业务逻辑,不然必然出现界面卡死、无响应的问题。
内容的提问来源于stack exchange,提问作者Marcin K
相关产品推荐
相关产品推荐

