Java Swing多人答题应用:如何仅响应首个KeyEvent
问题分析
你的问题核心在于:普通boolean变量不具备线程安全性,且Swing事件调度线程(EDT)会把快速连续的按键事件加入队列。第一个事件执行时虽设置了clicked=true,但处理完newQuestion()后立刻将clicked重置为false,此时队列中的第二个事件已开始执行,会错误判定当前题目未被作答,进而处理刚加载的新题目。
解决方案
1. 改用线程安全的原子布尔变量
用AtomicBoolean替代普通boolean,它的compareAndSet方法能原子性地检查并更新状态,确保同一时间只有一个作答请求能通过验证。
2. 提取通用处理逻辑
把重复的得分计算、题目更新逻辑抽成通用方法,减少代码冗余,降低出错概率。
3. 优化事件处理流程
确保只有成功获取「作答锁」的请求才能处理当前题目,完成后再解锁并加载新题目。
修改后的完整代码
import javax.swing.*; import java.awt.*; import java.awt.event.ActionEvent; import java.util.Random; import java.util.concurrent.atomic.AtomicBoolean; public class Test extends JFrame{ int right_score; int left_score; JLabel r_score = new JLabel(); JLabel l_score = new JLabel(); boolean answer; // 用AtomicBoolean保证线程安全的状态标记 AtomicBoolean isAnswered = new AtomicBoolean(false); JLabel answer_label = new JLabel(); public static void main(String[] args) {new Test();} public Test() { addKeyBindings(); r_score.setText("Right: 0"); l_score.setText("Left: 0"); answer_label.setText("" + answer); this.setLayout(new FlowLayout(FlowLayout.CENTER)); this.setMinimumSize(new Dimension(100, 100)); this.add(answer_label); this.add(l_score); this.add(r_score); newQuestion(); this.setVisible(true); } private void newQuestion() { Random rand = new Random(); answer = rand.nextBoolean(); answer_label.setText("" + answer); // 新题目加载完成后,重置作答标记 isAnswered.set(false); } // 通用作答处理方法 private void handleAnswer(boolean isRightPlayer, boolean isAnswerCorrect) { // 原子性检查是否已作答,只有未作答时才处理 if (isAnswered.compareAndSet(false, true)) { boolean isCorrect = isAnswerCorrect == answer; if (isRightPlayer) { right_score += isCorrect ? 1 : -1; r_score.setText("Right: " + right_score); } else { left_score += isCorrect ? 1 : -1; l_score.setText("Left: " + left_score); } // 处理完成后加载新题目 SwingUtilities.invokeLater(this::newQuestion); } } public void r_pressed_yes() { handleAnswer(true, true); }; public void r_pressed_no() { handleAnswer(true, false); }; public void l_pressed_yes() { handleAnswer(false, true); }; public void l_pressed_no() { handleAnswer(false, false); }; private void addKeyBindings() { Action r_pressed_yes = new AbstractAction() { public void actionPerformed(ActionEvent e) { r_pressed_yes(); }}; Action r_pressed_no = new AbstractAction() { public void actionPerformed(ActionEvent e) { r_pressed_no(); }}; Action l_pressed_yes = new AbstractAction() { public void actionPerformed(ActionEvent e) { l_pressed_yes(); }}; Action l_pressed_no = new AbstractAction() { public void actionPerformed(ActionEvent e) { l_pressed_no(); }}; r_score.getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW).put(KeyStroke.getKeyStroke("LEFT"), "r_pressed_yes"); r_score.getActionMap().put("r_pressed_yes", r_pressed_yes); r_score.getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW).put(KeyStroke.getKeyStroke("RIGHT"), "r_pressed_no"); r_score.getActionMap().put("r_pressed_no", r_pressed_no); r_score.getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW).put(KeyStroke.getKeyStroke("A"), "l_pressed_yes"); r_score.getActionMap().put("l_pressed_yes", l_pressed_yes); r_score.getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW).put(KeyStroke.getKeyStroke("D"), "l_pressed_no"); r_score.getActionMap().put("l_pressed_no", l_pressed_no); } }
关键说明
AtomicBoolean的compareAndSet:该方法会原子性检查当前值是否为false,若是则设置为true并返回true,否则返回false。即使多个按键事件同时触发,只有第一个能成功获取「作答权」,其他请求会直接被忽略,完美解决快速连续按键的问题。SwingUtilities.invokeLater:确保新题目加载操作在EDT上执行,保证UI更新的线程安全。- 通用方法
handleAnswer:统一重复的得分计算、UI更新逻辑,避免代码冗余,便于后续维护。
内容的提问来源于stack exchange,提问作者Ulala Olala
相关产品推荐
相关产品推荐

