0

我正在使用 KryoNet java 库和 slick 开发基于服务器/客户端的游戏。当服务器类接收到来自客户端的连接时,它会向客户端发送必要的启动信息,包括玩家编号。收到此消息后,客户端开始流畅并开始正常运行。代码是:

    boolean started = false;
    while(!started){
        System.out.println(cs.playerNum);
        if(cs.playerNum != -1){
            cs.startSlick();
            started = true;
        }
    }

当从服务器接收到值时,playerNum 由另一个线程设置。有一段时间我无法让它工作(从未调用 cs.startSlick()),最终我感到沮丧并开始在每次循环运行时记录 playerNum。通过添加 System.out.println(cs.playerNum),代码开始工作,循环将正确评估并启动 slick。

System.out.println 怎么可能做到这一点?我尝试用其他函数替换它,甚至其他将 cs.playerNum 作为参数的函数,但只有当我专门打印 cs.playerNum 时,我才能让循环工作。如果我需要包含更多源,我可以,但问题似乎直接在这里,因为我尝试用其他函数替换 System.out.println 没有成功。

4

2 回答 2

3

当您说“playerNum 由另一个线程设置”时,您有点回答了自己的问题。你所拥有的是一个经典的比赛条件。如果您的代码能够足够快地执行,那么 playerNum 将不会在需要时设置。但是,如果某些事情延迟或“中断”了您的代码,那么另一个线程将有时间设置 playerNum 值,并且您的代码将按预期工作。

执行 IO 的系统调用会在线程等待该 IO 操作发生时强制挂起该线程。当您调用 System.out.println 时会发生这种情况,这会导致您看似紧凑的代码暂停短暂让给另一个线程并允许您检索所需的值。

这是一个非常基本的线程问题,您将在编写线程代码时遇到更复杂的线程问题。因此,我绝对建议您花一些时间阅读一般的线程并了解同步函数如何工作以及 wait() 和 notify(),正如评论中所建议的那样。

于 2015-09-30T15:58:36.833 回答
2

睡眠并不能解决问题表明这不仅仅是给线程时间工作,而是关于跨线程的内存可见性。当您的线程调用 println 时,它会在控制台上获得一个锁,这会强制对 cs.playerNum 的更改变得可见。

你没有说你如何更新 playerNum 或者它是否是易变的,但你似乎看到了一个优化,JVM 不知道它需要让这个线程知道对 playerNum 的更新。JVM 可以进行优化,例如重新排序字节码或缓存值,并且只有在您的代码通过使变量易失或进行锁定等操作表明不允许这样做时才知道不要这样做。

应该避免忙等待,这确实需要替换为等待通知,或者从队列中读取,或者使用 java.util.concurrent 中的一些更高级别的构造。

于 2015-09-30T15:58:59.163 回答