为什么这个 print 语句会改变这个循环的执行方式?

Why is this print statement changing how this loop executes?

这是我的游戏循环线程中的 运行 方法(另一个 SO 答案的修改版本)。 运行ning 布尔值基本上是作为暂停和取消暂停游戏的一种方式,它正常工作得很好。我试图添加一个构造函数,让游戏以暂停状态或 运行ning == false 开始,但它没有用。尽管已成功将 运行 设置为真,但游戏永远不会开始。我添加了一些打印语句来弄清楚发生了什么。然后奇怪的事情发生了。添加打印语句解决了这个问题。

private void reset() {
    initialTime = System.nanoTime();
    deltaU = 0; deltaF = 0;
    frames = 0; ticks = 0;
    timer = System.currentTimeMillis();
}
public void run() {
    Thread.currentThread().setPriority(Thread.MAX_PRIORITY);

    reset();
    boolean wasRunning=false;
    while (true) {
        System.out.println("in loop");
        if (running) {
            System.out.println("running");
            if(!wasRunning){
                wasRunning=true;
                reset();
            }
            long currentTime = System.nanoTime();
            deltaU += (currentTime - initialTime) / timeU;
            deltaF += (currentTime - initialTime) / timeF;
            initialTime = currentTime;

            if (deltaU >= 1) {
                update();
                ticks++;
                deltaU--;
            }

            if (deltaF >= 1) {
                render();
                frames++;
                deltaF--;
            }

            if (System.currentTimeMillis() - timer > 1000) { //prints warning if dropped frames
                if (printTime) {
                    System.out.println(String.format("UPS: %s, FPS: %s", ticks, frames));
                }
                if(ticks!=60 || frames!=60)
                    System.out.println(String.format("UPS: %s, FPS: %s", ticks, frames));
                frames = 0;
                ticks = 0;
                timer += 1000;
            }
        }
        else
            wasRunning=false;
    }
}

输出是

in loop
running
in loop
running
in loop
running...

而且游戏 运行 有很多问题(尽管 fps 警告出奇地安静)。当我注释掉 System.out.println("in loop") 时,没有输出,设置 运行 true 也没有任何作用。

所以有一个 print 语句的事实实际上是导致循环执行,但是省略它会导致它失败。

请注意,如果 运行ning 首先设置为 true,打印语句将被删除,暂停和取消暂停功能将按预期工作。

我还尝试重命名 运行 运行ning2 以确保它没有覆盖任何内容。

tl;dr:您应该尝试将 running 标记为 volatile

我假设 running 是从另一个线程设置的?如果是这种情况,如果 running 最初是 false 并且仅被另一个线程设置为 true,那么它可能是竞争条件。

if 语句之前添加 println() 调用为另一个线程在检查之前将 running 修改为 true 增加了足够的时间。删除 println() 调用会导致在其他线程将其设置为 true.

之前检查 running

如果是这种情况,您真的应该重构您的代码以避免这种竞争情况。由于无法看到所有内容,因此很难就如何处理此问题提出建议。

编辑:阅读下面的评论后,似乎无限 while 循环会 "catch" 更改为 running,但这可能不是running 未标记为 volatile.

的情况

volatile 在 Java 中定义了语义,其中之一是变量永远不会在线程本地缓存,即对 volatile 变量的访问总是进入主内存并且总是反映当前状态,即使被其他线程更新。

请参阅 volatile 关键字上的 here for more information

编辑:添加 tl;dr.

编辑:Quick/layman对volatile的解释: JVM 在为提高效率而进行的优化数量方面可能非常核心。

对于方法中的非易失性字段访问,JVM 可能会假设没有其他线程正在访问它;在这样做时,它可能会制作该字段的 线程本地 副本,以便它可以修改它而不必处理访问 "main memory" 的争用。这提高了性能,但也有我们已经看到的缺点:如果另一个线程修改 running,当前线程将看不到它,因为它使用的是 local/contextual 到它自己的副本。

将字段标记为 volatile 修复了它,因为当前 Java 内存模型下 volatile 的语义基本上确保了所有 reads/writes volatile 字段返回到 "main memory",建立事前发生关系。

那么为什么不将所有内容都标记为 volatile?或者就此而言,为什么不是所有内容都隐含 volatile 而不是相反?答案是性能:易失性访问通常会比非易失性访问慢很多,因此 Java 语言设计者决定让它的使用成为可选的,只有当你知道你需要安全时。 (同样适用于 synchronized 访问)