You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.15 22:15:02