繁体   English   中英

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

[英]Wait for method to Finish, and weird interaction with System.println

我正在尝试编写一个遗传程序来玩游戏,但是我遇到了一些麻烦。 当我调用此代码时:

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);
    }
}

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

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

有多种方法可以确保可以看到更新。 最简单的方法是将isDone声明为volatile 另一个是在原始锁内进行读取和写入。

println()调用“修复”问题的原因是println在幕后进行了一些同步,这导致了偶然的缓存刷新(或类似的事情)使您的更新可见。 (换句话说:您很幸运,但是确切地讲如何幸运很难被束缚。)


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

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

  • 您可以使用LatchSemaphore或类似方法来实现等待。

  • 您可以使用提供FutureExecutor ,然后调用Future.get()等待交付结果。

  • 您甚至可以使用Object.waitObject.notify ...,尽管这是低级的并且容易出错。

如果没有看到完整的上下文,很难判断哪种方法最合适。 但是他们总比忙于等待更好。


另一个答案是这样的:

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

如上所述,真正的问题是同步不足。 要技术,需要有一个之前发生的写入的关系isDone在一个线程和读isDone在另一个。 各种各样的事情都会导致……但是没有那个,编译器有权假定:

  1. 写入线程不需要将写入刷新到内存
  2. 读取线程不需要检查内存是否已更改。

例如,如果没有thebefore-before ,则允许编译器进行优化

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

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

我们不知道这种情况是否真的发生,还是不知道对isDone更新的“不可见性”的真正原因是什么。 (实际上,它可能是特定于JVM版本/平台的。要确定,您将需要让JIT编译器为方法转储本机代码,并非常仔细地分析代码。)

显然,您是在单独的线程中运行游戏。 假设该线程名为foo ,则调用foo.join()将阻塞调用线程,直到foo完成执行为止。 您可以简单地将整个循环替换为foo.join() 如果您从循环中删除System.out.println()调用,我相信编译器根本不会在Java字节码中包含该循环,因为它是多余的。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM