Java不确定吗?

时间:2013-07-13 01:58:43

标签: java

我在Java中编写了一些捕食者 - 猎物模拟。即使规则非常复杂并且最终处于混乱系统中,所使用的技术也很简单:

  • 关于基本数据类型的算术和决定
  • 没有外部图书馆
  • 不包括外部系统
  • 没有并发发生
  • 不使用当前时间或日期

所以我认为在使用相同的参数初始化系统时,它应该输出相同的结果,但它没有,我想知道为什么。

对此的一些想法: 我的应用程序使用Random s,但对于该测试,我使用给定值初始化它们,因此根据我的理解,它们应该为每次运行创建相同顺序的相同输出。

我正在迭代Set,我知道没有定义迭代Set的顺序。但是我没有看到为什么以相同的值填充相同顺序的Set在多次运行中表现不同的原因。是吗?

我正在使用很多float s。 1 + 1 = 1.9999999999725的数据类型总是让我怀疑,但即使他们的行为对我来说很奇怪,也应该总是一样奇怪。不是吗?

垃圾收集不是确定性的,但只要我不依赖于析构函数,我就应该是安全的。

如上所述,根据实际使用时间,没有并发性和数据类型。

我无法在一个简单的例子中重现这种行为。但是通过我的代码,我看不到任何可能无法预测的事情。那么我上面的任何假设都是错误的吗?我有什么想法可以丢失吗?

这是验证我的假设的测试:

public static void main(String[] args) {
    Random r = new Random(1);
    Set<Float> s = new HashSet<Float>();
    for (int i = 0; i < 1000000; i++) {
        s.add(r.nextFloat());
    }

    float ret = 1;
    int cnt = 0;
    for (Float f : s) {
        float multiply = 0.3f;
        if (cnt++ % 2 == 0) {
            multiply = 0.7f;
        }
        float f2 = (f * multiply);
        ret += f2;
    }

    System.out.println(ret);
}

对我来说总是在242455.25。

2 个答案:

答案 0 :(得分:20)

您可以用Java编写确定性程序。你只需要消除非确定性的可能来源。

很难知道在没有看到实际代码的情况下可能导致非确定性的原因,以及该决定论的具体证据。

有许多库方法可能是非确定性行为的来源......取决于您如何使用它们。

例如,Object.hashcode()返回的值(第一次在实例上调用)是非确定性的。并且渗透到任何使用散列的库。它肯定会影响迭代它们时返回HashSetHashMap元素的顺序...如果元素类没有覆盖hashcode()

随机数生成器可能是也可能不是确定性的。如果它们是伪随机的并且用固定种子初始化,则每个生成的数字序列将是确定性的。

浮点算术应该是确定性的。对于算术表达式的任何(固定)输入集,结果应始终相同。 (我不确定浮点运算的确定性是否由JLS保证,但如果它在实践中发生则会非常奇怪。就像......你在破坏的硬件上运行。)


关注 ... strictfp和非决定论。

根据JLS 15.4

  

“在非FP严格的表达式中,为实现使用扩展指数范围来表示中间结果的一些余地被授予;粗略地说,净效应是计算可能产生”正确答案“在独占使用浮点值设置或双值设置可能导致上溢或下溢的情况下。”

这并不能说明实现在非FP严格表达式中有多少“余地”。但是,我认为这种余地不会扩展到允许非确定性行为。我曾经认为特定平台上的JIT编译器总是会为同一个表达式生成等效的本机代码,并且该代码将是确定性的。 (我看不出任何非确定性的原因......除非硬件本身具有非确定性浮点。)非确定性的另一个可能来源可能是JIT编译和解释代码的行为可能不同。但坦率地说,我认为允许这种情况发生是“坚果”......我想我们已经听说过了。

因此,虽然非FP严格的表达评估在理论上可能是非确定性的,但我认为我们应该对此进行折扣......除非有明确证据证明它在实践中发生。

(请注意,我说的是真正的非决定论,而不是平台差异。)

答案 1 :(得分:11)

  

我正在通过集合进行迭代,并且我知道迭代Set的顺序没有被定义。但是我没有看到为什么以相同的值填充相同顺序的Set在多次运行中应该表现不同的原因。是吗?

它可以。该实现可以自由使用,例如,对象在内存中的位置作为底层哈希表的键。这取决于垃圾收集何时运行。