Java 如何在渲染线程和;更新程序线程?

Java 如何在渲染线程和;更新程序线程?,java,synchronized,volatile,Java,Synchronized,Volatile,我有一个复杂的对象,我们称之为World,它包含其他包含玩家数据的对象、描述世界地图的对象等。它用于基于回合的游戏,我在屏幕上渲染World,但使用单独的线程来更新World,因为这需要几秒钟的时间来运行。在渲染线程中运行这一切只会冻结屏幕 这是如何在非渲染线程中更新世界,并将其传递回渲染线程: // copy the world that is used for rendering World world; synchronized (Renderer.this.sync) { world

我有一个复杂的对象,我们称之为
World
,它包含其他包含玩家数据的对象、描述世界地图的对象等。它用于基于回合的游戏,我在屏幕上渲染
World
,但使用单独的线程来更新
World
,因为这需要几秒钟的时间来运行。在渲染线程中运行这一切只会冻结屏幕

这是如何在非渲染线程中更新
世界
,并将其传递回渲染线程:

// copy the world that is used for rendering
World world;
synchronized (Renderer.this.sync) {
  world = Renderer.this.world;
}
World clone = Util.clone(world);

// update the world 
Updater.update(clone);

// pass the updated world back to the render thread
SwingUtilities.invokeLater(new Runnable() {
  public void run() {
    synchronized (Renderer.this.sync) {
      Renderer.this.world = clone;
    }
  }
});
以下是渲染的工作方式:

// member variable: our sync-object
public final Object sync = new Object();

// member variable for world
public World world;

public void render() {
  ... renders the world object...
}
以下是我的问题:

  • 这是否正确地将新世界对象传回渲染线程?我很确定对新克隆世界的引用是正确的,但是克隆世界的内容是否也与世界对象同步
  • 是否应使用“挥发性”以及如何使用
  • 例如
    world.getPlayer(index.getName()
    是否正确同步

不确定问题是否清楚?我会根据需要澄清的谢谢

我刚上了一门关于Spring框架的课程。讲师花了大量时间向我们证明,开发多线程应用程序是极其复杂的。即使您认为您将每一段敏感代码都包装在同步块中,您也可能会因为运行时优化(如分支预测等)而出现意外行为

volatile关键字用于控制这种不可预测的行为(即,对分支预测并行处理设置障碍)


我在回答中声明,我没有太多开发多线程应用程序的经验。话虽如此,我还是将World对象设计为一个单例对象,这样它就只有一个副本,一次只有一个线程可以访问它。

我刚上了一门关于Spring框架的课程。讲师花了大量时间向我们证明,开发多线程应用程序是极其复杂的。即使您认为您将每一段敏感代码都包装在同步块中,您也可能会因为运行时优化(如分支预测等)而出现意外行为

volatile关键字用于控制这种不可预测的行为(即,对分支预测并行处理设置障碍)


我在回答中声明,我没有太多开发多线程应用程序的经验。话虽如此,我还是将World对象设计为一个单例对象,这样它就只有一个副本,一次只有一个线程可以访问它。

回答第一个问题需要更多的澄清,因为代码不清楚。然而,对于你的第二个问题,我可以说你可以通过三种方式来做到:

  • 使用
    synchronized
    ,就像您在这里所做的那样(除了您没有向我们显示渲染器代码,所以我不能确定这一点)

  • 使用
    volatile

  • 使用
    原子参考

  • 这些都可以,但是你需要小心,把每件事都做好。您似乎非常小心,不要直接修改世界,而是在发布参考之前复制并在本地修改世界。这是件好事。但是你做了一些奇怪的事。我希望看到这样的情况:

    synchronized (Renderer.this.sync) {
      world = Renderer.this.world;
    }
    World clone = Util.clone(world);
    // update the world 
    Updater.update(clone);
    // publish the reference back to the renderer
    synchronized (Renderer.this.sync) {
      Renderer.this.world = clone;
    }
    
    这一切都是在非渲染器线程中完成的。然后,在发布引用后,您应该以某种方式通知渲染器,使其将已发布的引用复制到局部变量(同样,在
    同步的
    块中),然后对其进行渲染。这样可以确保它获得正确更新的世界,并且在渲染过程中不会热交换
    世界
    引用

    这基本上是关于两个操作:读取两个线程之间共享的引用和写入它。有一种东西叫做。适用于这种情况:

    • 监视器上的解锁发生在监视器上的每个后续锁定之前
    • 对易失性字段(§8.3.1.4)的写入发生在该字段的每次后续读取之前
    • AtomicReference.set
      发生在每个后续的
      AtomicReference.get
      之前(根据,get和set与volatile读/写具有相同的语义)
    这就是为什么您可以使用
    volatile
    字段或
    AtomicReference
    (两者都比
    synchronized
    提供更好的性能)执行完全相同的操作。只需使用
    volatile
    赋值或调用设置/获取原子引用来替换那些
    同步的
    赋值块

    现在,我们讨论的是引用本身的写入/读取。
    世界
    的字段或它引用的对象是什么?好的,在一个线程中发生的事情也会在关系之前发生,因为它们是按程序顺序出现的。因此,在将引用发布到克隆之前,无论您向
    世界
    对象的字段写入什么内容(或属于其对象图的任何内容,或任何其他相关内容),渲染线程都可以看到:

    clone.setSomeProperty(someValue); // this...
    synchronized (Renderer.this.sync) {
        Renderer.this.world = clone; // ...happens-before this, which in turn...
    }
    // ...
    synchronized (Renderer.this.sync) {
        world = Renderer.this.world; // ...happens-before this in the rendering thread
        // (assuming the rendering thread entered this block after the
        //  updating thread entered its block, otherwise we'll just see
        //  the old world here and will pick up the new one on the next try)
    }
    

    如果使用传递给
    SwingUtilities.invokeLater()
    的匿名
    Runnable
    等闭包将
    clone
    变量直接传递给另一个线程,会发生什么情况?这取决于如何实现
    SwingUtilities.invokeLater()
    。不幸的是,这些文件对这个问题并不十分清楚。我想这是安全的,但为了绝对肯定,我宁愿从当前线程安全地发布新引用(如上面的示例所示),或者依赖
    final
    字段语义,如下所示:

    SwingUtilities.invokeLater(new Runnable() {
      private final World world = clone; // final semantics enforced
      public void run() {
        Renderer.this.world = world; // this runs after the Runnable is constructed
        // and therefore is guaranteed to see the updated version of the world
      }
    });
    
    您应该参考文档,了解用于在渲染器线程中执行代码的OpenGL函数。如果它说调用
    runInRenderThread()
    发生在代码执行之前,那么您就安全了。