当前位置: 首页 > 知识库问答 >
问题:

为什么这个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上使用OpenJDK6。

共有2个答案

林俊晖
2023-03-14

由于currentpos是在线程外部更改的,因此应将其标记为volatile:

static volatile Point currentPos = new Point(1,2);

如果没有volatile,html" target="_blank">线程就不能保证读取主线程中对currentPos的更新。因此会继续为currentPos编写新的值,但出于性能原因,thread会继续使用以前的缓存版本。因为只有一个线程修改currentPos,所以您可以在没有锁的情况下离开,这将提高性能。

如果在线程中只读取一次值以用于比较和随后的显示,结果看起来会有很大的不同。当我执行以下操作时,x总是显示为1y0和某个大整数之间变化。我认为,在没有volatile关键字的情况下,它的行为在某种程度上是未定义的,可能是代码的JIT编译导致了它的这种行为。此外,如果注释掉空的synchronized(this){}块,那么代码也可以正常工作,我怀疑这是因为锁定引起了足够的延迟,currentpos及其字段被重新读取,而不是从缓存中使用。

int x = p.x + 1;
int y = p.y;

if (x != y) {
    System.out.println(x+" "+y);
    System.exit(1);
}
葛永丰
2023-03-14

很明显,在读取之前,写currentPos是不会发生的,但我不认为这会是问题所在。

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

因此,从读取线程的角度来看,使用新值读取x而使用默认值0读取y是合法的执行。当您到达println语句(顺便说一下,该语句是同步的,因此会影响读取操作)时,变量已经有了它们的初始值,程序将打印期望值。

currentpos标记为volatile将确保安全发布,因为您的对象实际上是不可变的--如果在您的实际用例中,对象在构造后发生了变化,volatile的保证是不够的,您可能会再次看到不一致的对象。

或者,您可以使不可变,这也将确保安全发布,即使不使用volatile。要实现不变性,只需将xy标记为final。

作为附带说明,正如前面提到的,synchronized(this){}可以被JVM视为no-op(我知道您包含它是为了再现该行为)。

 类似资料:
  • 问题内容: 今天,我实验室中的一个敏感操作完全出错。电子显微镜上的执行器越过边界,经过一连串的事件,我损失了1200万美元的设备。我将有故障的模块中的超过40K行缩小为: 我得到的一些输出示例: 由于这里没有任何浮点算法,而且我们都知道有符号整数在Java中的溢出情况下表现良好,因此我认为这段代码没有错。但是,尽管输出表明程序未达到退出条件,但程序仍达到了退出条件(是否达到 和 未达到?)。为什么

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

  • 你好Stack overflow社区, 我的提示是:**“您将进行错误检查,以确保输入的小时在[0,23]范围内。继续询问用户,直到用户输入有效范围内的时间。 当我的程序有正确的整数输入(任何整数输入)时,它将正常工作。然而,如果我输入一个非整数,那么所有的东西都卡在一个无限循环中。 我做错了什么?

  • 我在中使用带有4.1版本模拟器的android最新版本sdk。一切都很好。但是在我的中,对于任何应用程序的每次运行,我都会得到以下语句。 即使在Hello world应用程序中,我也得到相同的logcat输出。我没有在我的应用程序中使用多线程。有人能告诉我为什么在我的logcat中得到这些日志。 这是我的密码 在我的异步任务中,我从服务器获取JSONArray,解析它并列出。

  • 自从在JDK1.0中引入Object#Finalize()之后,Java又推出了9个版本。 然而,尽管它有明显的缺点,但它仍然可用,即使在JDK10中也是如此。 这就引出了一个问题:这种方法是否有任何已知的正确用法?

  • 大家好,我在项目中遇到了一个大问题。我发出OkHttp请求,从服务器收到响应,一切都很完美。问题是为什么我不能显示数据,我在xml和listview中创建了一个模型项(这是必需的视图),当我访问该页面时,该页面为空。 有人能帮我解决这个问题吗? 页面代码如下: