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

Java线程实现简易Ludo游戏的同步及报错问题排查

问题根因

你遇到的current thread is not owner(当前线程不是锁持有者)错误,是Java并发编程的常见基础错误:wait()、notify()、notifyAll()三个方法必须由持有目标对象监视器锁的线程调用,你的代码里两个核心设计缺陷直接触发了这个报错,同时也不满足线程同步的要求:

  • 你在finishTurn()方法中直接调用下一个Player对象的notify(),但执行该方法的当前回合线程,根本没有持有下一个Player对象的锁,直接触发非法监视器状态异常。
  • 每个Player线程在自身对象实例上加锁等待,锁资源是分散的,根本无法实现“同一时间仅一个线程操作共享游戏数据”的要求,多线程并发读写棋盘、玩家状态时必然出现数据错乱。
线程创建位置说明

你在Game.run()方法中创建并启动所有玩家线程的位置是合理的:Game作为全局游戏调度类,持有所有玩家、棋盘数据的引用,在这里统一管理线程生命周期符合职责划分。需要注意的是,启动所有线程后不要立刻触发回合切换,要等所有玩家线程都进入等待状态后再发出第一个回合信号,避免信号丢失导致线程永久阻塞。

修正后实现

核心调整思路:使用Game类实例作为全局唯一共享锁,所有玩家线程都竞争这把锁,所有对共享游戏数据的读写、wait/notify操作都基于这把锁执行,既解决锁持有者异常,也天然保证同一时间只有一个线程操作共享数据。

修正后的Player.java

public class Player implements Runnable {
    String name;
    Game game;
    // 回合标记,避免虚假唤醒
    private boolean isMyTurn = false;

    public Player(String name, Game game) {
        this.name = name;
        this.game = game;
    }

    public void setMyTurn(boolean myTurn) {
        isMyTurn = myTurn;
    }

    @Override
    public void run() {
        // 全局竞争Game实例锁,保证同一时间仅一个线程执行游戏逻辑
        synchronized (game) {
            while (!Thread.currentThread().isInterrupted()) {
                try {
                    // 未轮到自己则持续等待
                    while (!isMyTurn) {
                        game.wait();
                    }
                    // 执行回合逻辑
                    System.out.println(name + " 的回合开始");
                    // 此处编写掷骰子、移动棋子、碰撞判定等具体游戏逻辑

                    // 回合结束,重置自身标记,触发下一个玩家回合
                    isMyTurn = false;
                    game.finishTurn(this);
                    
                    // 产生获胜者后退出线程循环
                    if (game.getWinner() != null) {
                        break;
                    }
                } catch (InterruptedException e) {
                    Thread.currentThread().interrupt();
                    throw new RuntimeException(e);
                }
            }
        }
    }

    @Override
    public String toString() {
        return name;
    }
}

修正后的Game.java

public class Game implements Runnable {
    private List<Player> players;
    private List<Player> fields;
    private int players_finished;
    private Player winner;

    public Game() {
        this.players = new ArrayList<>();
        this.fields = new ArrayList<>();
        this.players_finished = 0;
        this.winner = null;
    }

    public Player getWinner() {
        return winner;
    }

    public void init()
    {
        System.out.println("游戏启动");
        players.add(new Player("Magnum", this));
        players.add(new Player("Felix", this));
        players.add(new Player("Alex", this));
        players.add(new Player("Sandra", this));

        for(int i = 0; i < 20; i++) {
            fields.add(i, null);
        }

        for(int i = 0; i < 20; i++) { // 每隔5格放置玩家初始棋子
            if(i % 5 == 0)
            {
                fields.set(i, players.get((int)i / 5));
            }
        }
    }

    @Override
    public void run()
    {
        List<Thread> playerThreads = new ArrayList<>();
        for(Player player : players) {
            Thread t = new Thread(player);
            playerThreads.add(t);
            t.start();
        }

        // 等待所有玩家线程进入等待状态,避免启动信号丢失
        try {
            Thread.sleep(100);
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
        }

        // 持有全局锁后通知第一个玩家开始回合
        synchronized (this) {
            players.get(0).setMyTurn(true);
            notifyAll();
        }

        // 等待所有玩家线程执行完毕
        for(Thread t : playerThreads) {
            try {
                t.join();
            } catch (InterruptedException e) {
                Thread.currentThread().interrupt();
            }
        }
        System.out.println("游戏结束,获胜者为:" + winner);
    }

    void finishTurn(Player player)
    {
        System.out.println(player + " 完成回合");
        int currentIndex = players.indexOf(player);
        // 此处可添加玩家完赛判定逻辑:如果当前玩家所有棋子到达终点,players_finished计数+1,满足获胜条件则设置winner
        
        // 计算下一个玩家索引,循环轮转
        int nextIndex = (currentIndex + 1) % players.size();
        // 可添加逻辑跳过已完赛的玩家
        Player nextPlayer = players.get(nextIndex);
        nextPlayer.setMyTurn(true);
        // 唤醒所有等待在全局锁上的线程,仅标记为isMyTurn的玩家会执行逻辑
        notifyAll();
    }
}

Main.java 无需修改,保持原有逻辑即可

public class Main {
    public static void main(String[] args) {
        Game game = new Game();
        game.init();
        game.run();
    }
}
关键注意事项
  • 所有wait()调用必须放在循环条件判断中,不要用if单次判断,避免JVM的虚假唤醒机制导致逻辑错乱。
  • 所有对玩家列表、棋盘格子、获胜状态等共享数据的读写操作,必须放在持有Game全局锁的代码块中执行,从根源上避免并发数据不一致问题。
  • 不要使用单个Player实例作为锁对象,否则调用notify时必须持有对应Player的锁,不仅编码容易出错,也无法实现全局的互斥访问控制。

内容的提问来源于stack exchange,提问作者Deutrys

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.30 01:57:24