【问题标题】:Java System.out.println() affecting program flow [duplicate]Java System.out.println() 影响程序流程 [重复]
【发布时间】:2015-09-30 15:45:29
【问题描述】:

我正在使用 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 没有成功。

【问题讨论】:

  • 有没有线程掉线? System.out.println 会导致当前线程暂停,允许其他线程做那里的事情。
  • 不知道您的问题的答案,但这通常是非常糟糕的代码!如果一个线程必须等待另一个线程,请使用 wait() 和 notify()!忙碌的等待从来都不是一个好的选择,而且会导致像你这样的奇怪行为!
  • 公平地说,我以前没有使用过多线程应用程序,所以我完全有可能做错了什么。我会考虑使用您的建议,但我仍然对正在发生的事情感到好奇。
  • @mlk 可能就是这样。如果另一个线程被启动并允许自己运行,那么在这个线程运行时它实际上不会做任何事情吗?另外,我尝试过 sleep() ing这个线程,但这并不能解决问题:那会做同样的事情吗?抱歉,我有点困惑,以前没有太多使用过线程。
  • 发生了什么:另一个线程修改了started变量,但是这个修改对于这个线程是不可见的。 (原因隐藏在缓存和 Java 内存模型中)。 可以通过将 started 变量声明为 volatile(以确保每个线程都能看到更改)来解决,但是你正在做的是被称为“忙着等待”,这是一种非常不好的做法,除非您的目标是使用 CPU 来保持咖啡热气腾腾。你应该看看java.util.concurrent,也许用CountDownLatch 左右来解决它...

标签: java multithreading slick system.out kryonet


【解决方案1】:

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

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

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

【讨论】:

  • 谢谢。我确实有点跳进去,我想我需要一些时间来了解更多。关于从哪里开始有什么特别好的建议,还是我应该直接上谷歌?
  • 这不是我所说的“竞争条件”(尽管我不想在这里争论措辞)。这只是 Java 内存模型所暗示的可见性问题。
  • @Marco13,似乎您确实想争论或者您不会发表评论。也许谷歌也是错误的,但“竞争条件”的答案 - 竞争条件是不可取的当设备或系统尝试同时执行两个或多个操作时发生的情况,但由于设备或系统的性质,必须按照正确的顺序执行操作才能正确完成。
  • @htuy42 加入并没有错。但在我看来,线程是典型程序员会遇到的最困难的事情之一,而且肯定有很多陷阱。坚持下去,它就会开始点击。
  • @AaronM 顺序是: 1. 必须更改变量。 2. 必须检查变量(这里任何时候...)。就序列而言,一切都很好。更改对其他线程不可见 的事实与此无关。这主要是一个挑剔,因为我认为一个答案(被接受)应该谨慎措辞,以免提出错误的建议。
【解决方案2】:

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

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

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

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2011-03-25
    • 2013-05-24
    • 1970-01-01
    • 2019-07-19
    • 1970-01-01
    • 2017-10-02
    • 2017-01-12
    • 1970-01-01
    相关资源
    最近更新 更多