我正在使用KryoNet java库以及光滑的基于服务器/客户端的游戏。当服务器类从客户端接收连接时,它会向客户端发送必要的启动信息,包括它是什么玩家号码。接收到这个,客户端开始光滑并开始正常运行。代码是:
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),代码开始工作,循环将正确评估并且将启动光滑。
System.out.println怎么可能这样做?我试过用其他函数替换它,甚至用cs.playerNum作为参数的其他函数,但只有当我专门打印cs.playerNum时才能让循环工作。 如果我需要包含更多源代码,但问题似乎就在这里,因为我尝试用其他函数替换System.out.println没有成功。
答案 0 :(得分:3)
当你说“玩家数量由另一个线程设置”时,你回答了自己的问题。你拥有的是一个经典的竞争条件。如果您的代码能够足够快地执行,那么在需要时就不会设置该playerNum。但是,如果有什么东西要延迟或“中断”你的代码,那么另一个线程将有时间设置playerNum值,你的代码将按预期工作。
执行IO的系统调用在等待IO操作发生时强制挂起线程。当您调用System.out.println时,会发生这种情况,这会导致您看似严密的代码暂时停留在另一个线程上,然后允许您检索所需的值。
这是一个非常基本的线程问题,您将遇到编写线程代码的更复杂的线程问题。因此,我肯定建议您花一些时间阅读一般的线程并理解同步函数的工作方式以及wait()和notify(),如评论中所建议的那样。
答案 1 :(得分:2)
睡眠无法解决问题表明这不仅仅是让线程有时间工作,而是关于线程内存可见性。当您的线程调用println时,它会获取控制台上的锁定,这会强制更改cs.playerNum变为可见。
你没有说你如何更新playerNum或者它是否是易失性的,但是你似乎看到了JVM不知道需要让这个线程知道playerNum更新的优化。 JVM可以进行优化,例如重新排序字节码或缓存值,并且只有在代码通过执行诸如使变量变为易失或执行锁定之类的操作时才知道不执行此操作。
应该避免繁忙的等待,这实际上需要替换为等待通知,或从队列中读取,或者使用java.util.concurrent中的某些更高级别的构造。