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

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.30 21:12:19