使用ActionListener的wait/notify阻塞,无法显示按钮返回Player
问题分析与解决方案
你的代码出现窗口卡住、按钮无法显示的问题,核心原因是违反了Swing的单线程模型:你在主线程(非事件派发线程EDT)中调用了wait(),导致EDT无法执行窗口渲染和事件处理的任务——Swing所有UI相关的操作(包括组件绘制、事件响应)都必须在EDT上完成,主线程阻塞后,EDT被卡死,窗口自然没法正常显示。
另外原代码还有几个可以优化的点:比如通过循环匹配按钮来查找Player的逻辑冗余,静态变量pReturn存在线程安全风险,以及没有在按钮点击后关闭窗口。
修复方案:使用模态对话框替代wait/notify
模态对话框(JDialog)天生就具备「阻塞当前线程直到对话框关闭」的特性,完全适配你的需求,而且符合Swing的线程规范。下面是修改后的代码:
import javax.swing.*; import java.awt.*; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.util.ArrayList; public class PlayerSelector { // 去掉不必要的静态变量,改用局部变量管理选中的Player public static Player playerSelection(ArrayList<Player> players, String title) { final Player[] selectedPlayer = new Player[1]; // 用数组保存选中结果,方便在匿名类中修改 JDialog dialog = new JDialog((Frame) null, title, true); // 创建模态对话框 JPanel panel = new JPanel(new GridLayout(-1, 1, 5, 5)); for (Player player : players) { JButton btn = new JButton(player.getName()); panel.add(btn); btn.addActionListener(new ActionListener() { @Override public void actionPerformed(ActionEvent e) { selectedPlayer[0] = player; // 直接绑定当前Player,无需循环查找 dialog.dispose(); // 点击后关闭对话框 } }); } dialog.add(panel); dialog.pack(); dialog.setLocationRelativeTo(null); // 居中显示 dialog.setVisible(true); // 显示模态对话框,当前线程会阻塞直到对话框关闭 return selectedPlayer[0]; // 返回选中的Player,未选中则返回null } // 示例Player类 static class Player { private String name; public Player(String name) { this.name = name; } public String getName() { return name; } } }
关键改进点说明
- 使用模态JDialog:
setVisible(true)会阻塞当前线程,直到对话框被关闭,完全替代了你原来的wait/notify逻辑,且符合Swing线程规范。 - 直接绑定Player对象:在创建按钮时就捕获对应的
Player,避免了原代码中循环匹配按钮的冗余逻辑,代码更简洁高效。 - 移除静态变量:用局部数组
selectedPlayer保存选中结果(因为匿名内部类只能访问final局部变量,数组是引用类型可以修改内部值),避免了静态变量带来的线程安全问题。 - 自动关闭窗口:点击按钮后调用
dialog.dispose()关闭对话框,同时解除线程阻塞。
原代码的核心问题复盘
- 线程阻塞导致EDT无法工作:你在主线程中调用
o.wait(),主线程被挂起,但Swing的EDT需要主线程释放资源才能完成窗口绘制,导致窗口一直处于未渲染状态。 - 静态变量的线程风险:
pReturn是静态变量,如果多个地方调用playerSelection方法,会出现结果被覆盖的问题。 - 未关闭窗口:原代码中点击按钮后只调用了
o.notify(),没有关闭窗口,用户体验不好。
内容的提问来源于stack exchange,提问作者BurningToast
相关产品推荐
相关产品推荐

