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

Java中notifyAll()无法唤醒wait()暂停的线程如何解决?

Java Socket传球游戏notifyAll无法唤醒wait线程问题解决

问题背景

多人传球Socket游戏逻辑如下:

  • 客户端连接服务端后先判断自身是否持有球(hasBall)
  • 持有球可调用方法将球传给其他客户端
  • 未持有球则调用lock.wait()进入等待接收球的状态

问题现象

调用lock.notifyAll()后,无法唤醒处于lock.wait()状态的线程。具体表现为:
第一个加入的客户端默认持有球,第二个加入的客户端无球进入等待状态;客户端1将球传给客户端2后,客户端1更新为无球状态,客户端2始终没有更新为持有球状态。

问题根因

  1. wait/notify是同JVM进程内的线程同步机制:每个客户端是独立运行的进程,进程1内调用的notifyAll只能唤醒进程1内的等待线程,完全无法影响进程2内的等待线程,跨进程的状态同步不能依赖这套API。
  2. 状态值缓存未更新:原代码中hasBall的取值是在while(true)循环最开始计算的,进入synchronized块的等待循环后,哪怕线程被唤醒,判断条件用的还是循环开始时缓存的旧值,永远不会更新为最新的持球状态,导致线程被唤醒后又立刻进入等待。
  3. 通知触发时机错误:原代码只有本客户端执行传球操作时才会调用notifyAll,当其他客户端传球给当前客户端时,当前客户端没有任何触发notifyAll的逻辑,等待线程永远不会被主动唤醒。

修复方案

  1. 新增服务端推送逻辑:服务端收到任意客户端的传球请求、更新完全局持球人信息后,要主动给所有在线客户端推送最新的持球人数据,不能等客户端轮询拉取。
  2. 修正状态判断逻辑:等待循环的判断条件每次都要拉取最新的持球人信息,禁止使用循环开头缓存的旧值,修改后代码如下:
try{
    synchronized (lock){
        // 每次判断都拉取服务端最新的持球状态,不使用外层缓存值
        while (!Objects.equals(client.getPlayerId(), client.getPlayerWithBall())){
            receive();
            lock.wait();
            System.out.println("resumed");
        }
    }
}catch (Exception e){
    e.printStackTrace();
}
  1. 新增客户端收到推送后的唤醒逻辑:每个客户端收到服务端推送的最新持球人信息后,立刻调用lock.notifyAll()唤醒自身进程内的等待线程,重新判断持球状态。

原问题相关代码

import java.util.*;
import java.util.concurrent.CountDownLatch;

public class ClientInterface extends Thread {

    private final Client client;
    final static Object lock = new Object();


    public ClientInterface(Client client){
        this.client = client;
    }


    public void pass(String playerId) throws Exception {
            Scanner scanner = new Scanner(System.in);
            System.out.println("\nYou currently have the ball.");
            System.out.println("Enter ID of player you wish to pass to...");
            String line = scanner.nextLine();
            client.passBall(playerId, line);
            System.out.println("Pass ball to player #" + line);

    }

    public void receive() throws InterruptedException {
            System.out.println("waiting");
            System.out.println("\nPlayer #" + client.getPlayerWithBall() + " currently has the ball.");
    }


    @Override
    public void run() {
        try {

                System.out.println("Connected to Ball Game successfully.\n");

                while (true) {

                    // Get the list of all players currently connected to the server
                    String[] players = client.getPlayers();
                    // Get the ID generated for this client
                    String playerId = client.getPlayerId();
                    boolean hasBall = Objects.equals(playerId, client.getPlayerWithBall());

                    System.out.println("Your ID is : #" + playerId + '\n');
                    System.out.println("List of current in-game players:");
                    int count = 1;
                    //Print the ID strings of each player
                    for (String player : players) {
                        System.out.println("[" + count + "] ID: #" + player);
                        count++;
                    }

                    // Check if the player currently has the ball or not
                    // If they don't have the ball, print waiting, and call wait on object lock
                    try{
                        synchronized (lock){
                            while (!hasBall){
                                receive();
                                lock.wait();
                                System.out.println("resumed");
                            }
                        }
                    }catch (Exception e){
                        e.printStackTrace();
                    }

                    // Allow player with ball to send ball to another client
                    // call notify on object lock so thread can unlock if they now have the ball
                    synchronized (lock){
                        try {
                            pass(playerId);
                            lock.notifyAll();
                        }catch (Exception e){
                            e.printStackTrace();
                        }
                    }

                }

        } catch (Exception e) {
            System.out.println(e.getMessage());
            e.printStackTrace();
        }
    }


    public static void main(String[] args) throws Exception {
        Client client = new Client();
        ClientInterface c = new ClientInterface(client);
        if(client.getPlayers().length > 0){
            c.start();
        }
    }
}

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.09.25 21:24:01