当前位置: 首页 > 面试题库 >

为什么尽管显然不应该(也没有)终止该Java程序?

殳飞扬
2023-03-14
问题内容

今天,我实验室中的一个敏感操作完全出错。电子显微镜上的执行器越过边界,经过一连串的事件,我损失了1200万美元的设备。我将有故障的模块中的超过40K行缩小为:

import java.util.*;

class A {
    static Point currentPos = new Point(1,2);
    static class Point {
        int x;
        int y;
        Point(int x, int y) {
            this.x = x;
            this.y = y;
        }
    }
    public static void main(String[] args) {
        new Thread() {
            void f(Point p) {
                synchronized(this) {}
                if (p.x+1 != p.y) {
                    System.out.println(p.x+" "+p.y);
                    System.exit(1);
                }
            }
            @Override
            public void run() {
                while (currentPos == null);
                while (true)
                    f(currentPos);
            }
        }.start();
        while (true)
            currentPos = new Point(currentPos.x+1, currentPos.y+1);
    }
}

我得到的一些输出示例:

$ java A
145281 145282
$ java A
141373 141374
$ java A
49251 49252
$ java A
47007 47008
$ java A
47427 47428
$ java A
154800 154801
$ java A
34822 34823
$ java A
127271 127272
$ java A
63650 63651

由于这里没有任何浮点算法,而且我们都知道有符号整数在Java中的溢出情况下表现良好,因此我认为这段代码没有错。但是,尽管输出表明程序未达到退出条件,但程序仍达到了退出条件(是否达到
未达到?)。为什么?

我注意到这在某些环境中不会发生。我在64位Linux 上使用OpenJDK
6。


问题答案:

显然,在读取currentPos之前不会发生写入,但是我看不出这可能是个问题。

currentPos = new Point(currentPos.x+1, currentPos.y+1);做一些事情,包括将默认值写入xy(0),然后将其初始值写入构造函数。由于没有安全地发布您的对象,因此编译器/
JVM可以自由地重新排序这4个写操作。

因此,从读取线程的角度来看,x以新值但y默认值为0进行读取是合法的执行。当您到达该println语句时(顺便说一下,该语句已同步,因此确实会影响读取操作),这些变量具有其初始值,程序将打印期望值。

标记currentPosvolatile可以确保安全发布,因为您的对象实际上是不可变的-
如果在实际使用情况下,对象在构造后发生了突变,则volatile保证不够,您可能会再次看到不一致的对象。

或者,您可以使Point不可变,即使不使用也可以确保安全发布volatile。要实现不变性,您只需要标记xy最后确定即可。

附带说明一下,正如已经提到的,synchronized(this) {}JVM可以将其视为无操作(我知道您将其包括在内可以重现此行为)。



 类似资料:
  • 今天我实验室的一个敏感操作完全出了问题。一台电子显微镜上的一个致动器越过了它的边界,在一系列事件之后,我损失了1200万美元的设备。我将故障模块中的40K行缩小到以下范围: 我得到的一些输出示例: 因为这里没有任何浮点运算,而且我们都知道有符号整数在Java溢出时表现良好,所以我认为这段代码没有问题。然而,尽管输出表明程序没有达到退出条件,但它却达到了退出条件(既达到了又没有达到?)。为什么? 我

  • 我有一个简单的功能,我想测试,但明显的结果是没有发生。。。 我的函数是如何工作的(实际上它确实工作,只是没有正确测试) 我将字符串传递到函数中 当我运行所示的测试时,我收到错误: 预期默认值等于hare失败 我的组件 我的测试

  • 问题内容: 考虑以下程序: 为什么它在本地终止而不在Playground终止?我的程序终止是否依赖未定义的行为? 问题答案: 该代码不能提供太多保证。它几乎完全依赖于围绕未定义行为的实现细节。 在大多数多线程系统中,不能保证一个线程中的更改不会出现障碍。您有一个goroutine,可以在另一个处理器上运行,总共将一个值写入一个没有人保证读取的变量。 将可以很容易地重写,因为从未有一个保证该变量的任

  • 我的老师让我这样做,但在评论区我被告知我不应该这样做。 为什么?

  • 问题内容: 触发服务器调用以获取componentWillMount生命周期方法中的数据是一种不好的做法? 以及为什么最好使用componentDidMount。 问题答案: 更新: componentWillMount将很快被弃用。 引用@Dan Abramov 在 React的未来版本中,我们期望componentWillMount 在某些情况下 会触发多次 ,因此您应该对网络请求使用comp

  • 我读了一些帖子(特别是BalusC帖子)并在谷歌上搜索了原因(不深入),但我找不到为什么不应该使用实体bean作为托管bean。原因是什么?(我正在学习“ProJSF和HTML5”,在本书中,实体bean被用作托管bean。)