Java 等待方法完成,然后与System.println进行奇怪的交互

Java 等待方法完成,然后与System.println进行奇怪的交互,java,wait,Java,Wait,我正试图编写一个基因程序来完成一个游戏,但我遇到了一点障碍。当我调用此代码时: public double playMap (GameBoard gb, Player p) { gb.playerController = p; Game g = new Game(gb); int initHP = 0; for (Unit u : gb.enemy.units) { initHP += u.maxHP; } g.playGame(

我正试图编写一个基因程序来完成一个游戏,但我遇到了一点障碍。当我调用此代码时:

public double playMap (GameBoard gb, Player p) {
    gb.playerController = p;
    Game g = new Game(gb);
    int initHP = 0;
    for (Unit u : gb.enemy.units) {
        initHP += u.maxHP;
    }

    g.playGame(false);

    int finalHP = 0;
    for (Unit u : gb.enemy.units) {
        finalHP += u.currHP;
    }
    System.out.println("        " + initHP);
    System.out.println("        " + finalHP);
    System.out.println("        " + (finalHP - initHP));
    if (initHP == finalHP) {
        return -10;
    }
    return initHP - finalHP;
}
g.playGame()行没有时间完成,我从函数中得到了错误的结果。我可以等一个单位,比赛结束了

while (!g.isDone) {
    System.out.println(g.isDone);
}
但不使用相同的while循环,而不使用print语句。我知道必须有一个更优雅的解决方案,而且我似乎无法实现我所看到的方法。另外,如果有人知道为什么我需要while循环中的print语句来让它等待,那也太好了

提前谢谢

新增游戏:

public void playGame(boolean visual) {
    Global.visual = visual;
    if (Global.visual) {
        JFrame application = new JFrame();
        application.setBackground(Color.DARK_GRAY);
        application.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);

        application.add(this);           


        application.setSize(500, 400);         // window is 500 pixels wide, 400 high
        application.setVisible(true); 
    }

    PlayerInput pi = new PlayerInput();
    this.addKeyListener(pi);

    final Timer timer = new Timer(10/60, null);
    ActionListener listener = new ActionListener() {
        @Override 
        public void actionPerformed(ActionEvent e) {
            pi.addPressed();
            if (update(pi)) {
//                  application.setVisible(false);
//                  application.dispose();
                System.out.println(gb.toString());
                isDone = true;
                timer.stop();

            }
            pi.reset();
        }
    };
    timer.addActionListener(listener);
    timer.start();


    while (!isDone) {
        System.out.println(isDone);
    }
}

显然,你是在一个单独的线程中运行你的游戏。假设线程被调用为
foo
,调用
foo.join()
将阻止调用线程,直到
foo
完成执行。只需将整个循环替换为
foo.join()

如果从循环中删除
System.out.println()
调用,我相信编译器根本不在Java字节码中包含循环,认为它是多余的。

显然,您是在一个单独的线程中运行游戏。假设线程被调用为
foo
,调用
foo.join()
将阻止调用线程,直到
foo
完成执行。只需将整个循环替换为
foo.join()

如果从循环中删除
System.out.println()
调用,我认为编译器根本不在Java字节码中包含循环,认为它是多余的。

首先,这是一种非常糟糕的方法。这种方法称为“忙等待”,效率很低

问题很可能是对
g.isDone
的读写未正确同步。因此,无法保证“等待”线程会看到将其设置为true的
g.isDone
更新

有多种方法可以确保看到更新。最简单的方法是将
isDone
声明为
volatile
。另一种方法是在原语锁中执行读写操作

println()
调用“fixes”的原因是println在幕后进行了一些同步,这会导致意外的缓存刷新(或其他事情),从而使更新可见。(换句话说:你很幸运,但你是如何幸运的很难确定。)


更好的解决方案是使用另一种机制来协调两个线程

  • 您可以使用
    Thread.join()
    使一个线程等待另一个线程(完全!)终止

  • 您可以使用
    锁存器
    信号灯
    或类似工具来实现等待

  • 您可以使用交付
    未来的
    执行器
    ,然后调用
    Future.get()
    ,等待该执行器交付结果

  • 您甚至可以使用
    Object.wait
    Object.notify
    。。。虽然这是低级的,而且很容易出错

如果看不到全部情况,就很难判断哪种方法最合适。但他们都比忙着等待要好


另一个答案是:

如果从循环中删除System.out.println()调用,我相信编译器不会在Java字节码中包含该循环,认为它是多余的

如上所述,真正的问题是同步不足。从技术上讲,在一个线程中写入
isDone
和在另一个线程中读取
isDone
之间需要有一种先发生后发生的关系。各种各样的东西都会让你。。。但如果没有这一点,编译器有权假设:

  • 写入线程不需要刷新对内存的写入
  • 读取线程不需要检查内存是否已更改
  • 例如,如果没有之前发生的事件,编译器将被允许进行优化

    while (!g.isDone) {
         // do nothing
    }
    


    我们不知道这是否真的发生了,或者更新到
    isDone
    的“不可见性”的实际原因是否是其他原因。(事实上,它可能是JVM版本/平台特定的。当然,您需要让JIT编译器转储方法的本机代码,并非常仔细地分析代码。)

    首先,这是一种非常糟糕的方法。这种方法称为“忙等待”,效率很低

    问题很可能是对
    g.isDone
    的读写未正确同步。因此,无法保证“等待”线程会看到将其设置为true的
    g.isDone
    更新

    有多种方法可以确保看到更新。最简单的方法是将
    isDone
    声明为
    volatile
    。另一种方法是在原语锁中执行读写操作

    println()
    调用“fixes”的原因是println在幕后进行了一些同步,这会导致意外的缓存刷新(或其他事情),从而使更新可见。(换句话说:你很幸运,但你是如何幸运的很难确定。)


    更好的解决方案是使用另一种机制来协调两个线程

    • 您可以使用
      Thread.join()
      使一个线程等待另一个线程(完全!)终止

    • 您可以使用
      锁存器
      信号灯
      或类似工具来实现等待

    • 您可以使用交付
      未来的
      执行器
      ,然后调用
      Future.get()
      ,等待该执行器交付结果

    • 您甚至可以使用
      Object.waitif (!g.isDone) {
           // do nothing
      }