Java中notifyAll()无法唤醒wait()暂停的线程如何解决?
Java Socket传球游戏notifyAll无法唤醒wait线程问题解决
问题背景
多人传球Socket游戏逻辑如下:
- 客户端连接服务端后先判断自身是否持有球(hasBall)
- 持有球可调用方法将球传给其他客户端
- 未持有球则调用
lock.wait()进入等待接收球的状态
问题现象
调用lock.notifyAll()后,无法唤醒处于lock.wait()状态的线程。具体表现为:
第一个加入的客户端默认持有球,第二个加入的客户端无球进入等待状态;客户端1将球传给客户端2后,客户端1更新为无球状态,客户端2始终没有更新为持有球状态。
问题根因
wait/notify是同JVM进程内的线程同步机制:每个客户端是独立运行的进程,进程1内调用的notifyAll只能唤醒进程1内的等待线程,完全无法影响进程2内的等待线程,跨进程的状态同步不能依赖这套API。- 状态值缓存未更新:原代码中
hasBall的取值是在while(true)循环最开始计算的,进入synchronized块的等待循环后,哪怕线程被唤醒,判断条件用的还是循环开始时缓存的旧值,永远不会更新为最新的持球状态,导致线程被唤醒后又立刻进入等待。 - 通知触发时机错误:原代码只有本客户端执行传球操作时才会调用
notifyAll,当其他客户端传球给当前客户端时,当前客户端没有任何触发notifyAll的逻辑,等待线程永远不会被主动唤醒。
修复方案
- 新增服务端推送逻辑:服务端收到任意客户端的传球请求、更新完全局持球人信息后,要主动给所有在线客户端推送最新的持球人数据,不能等客户端轮询拉取。
- 修正状态判断逻辑:等待循环的判断条件每次都要拉取最新的持球人信息,禁止使用循环开头缓存的旧值,修改后代码如下:
try{ synchronized (lock){ // 每次判断都拉取服务端最新的持球状态,不使用外层缓存值 while (!Objects.equals(client.getPlayerId(), client.getPlayerWithBall())){ receive(); lock.wait(); System.out.println("resumed"); } } }catch (Exception e){ e.printStackTrace(); }
- 新增客户端收到推送后的唤醒逻辑:每个客户端收到服务端推送的最新持球人信息后,立刻调用
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
相关产品推荐
相关产品推荐

