为什么这个Java程序终止,尽管显然它不应该(而没有)? [英] Why does this Java program terminate despite that apparently it shouldn't (and didn't)?

查看:196
本文介绍了为什么这个Java程序终止,尽管显然它不应该(而没有)?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

今天我的实验室的敏感操作完全错误。电子显微镜上的致动器越过其边界,在一系列事件后,我损失了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!= py){
System.out.println(p.x ++ py);
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);
}
}

我得到的输出的一些示例: / p>

  $ 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); 做一些事情,包括将默认值写入 x y (0),然后将其初始值构造函数。因为你的对象没有被安全地发布,这4个写操作可以被编译器/ JVM自由地重新排序。



所以从读线程的角度来看,这是一个合法的执行使用其新值读取 x ,例如使用其默认值为0的 y 。当你到达 println 语句(顺便说一句,它是同步的,因此影响读操作),变量有它们的初始值,程序打印期望值



标记 currentPos volatile 因为你的对象是有效的不可变的 - 如果在你的实际使用情况下对象在构造之后突变, volatile 保证将是不够的,你可以再次看到一个不一致的对象。 / p>

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



作为旁注和前面已经提到, synchronized(this){} 可以被JVM视为一个无操作重现行为)。


A sensitive operation in my lab today went completely wrong. An actuator on an electron microscope went over its boundary, and after a chain of events I lost $12 million of equipment. I've narrowed down over 40K lines in the faulty module to this:

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

Some samples of the output I'm getting:

$ 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

Since there isn't any floating point arithmetic here, and we all know signed integers behave well on overflow in Java, I'd think there's nothing wrong with this code. However, despite the output indicating that the program didn't reach the exit condition, it reached the exit condition (it was both reached and not reached?). Why?


I've noticed this doesn't happen in some environments. I'm on OpenJDK 6 on 64-bit Linux.

解决方案

Obviously the write to currentPos doesn't happen-before the read of it, but I don't see how that can be the issue.

currentPos = new Point(currentPos.x+1, currentPos.y+1); does a few things, including writing default values to x and y (0) and then writing their initial values in the constructor. Since your object is not safely published those 4 write operations can be freely reordered by the compiler / JVM.

So from the perspective of the reading thread, it is a legal execution to read x with its new value but y with its default value of 0 for example. By the time you reach the println statement (which by the way is synchronized and therefore does influence the read operations), the variables have their initial values and the program prints the expected values.

Marking currentPos as volatile will ensure safe publication since your object is effectively immutable - if in your real use case the object is mutated after construction, volatile guarantees won't be enough and you could see an inconsistent object again.

Alternatively, you can make the Point immutable which will also ensure safe publication, even without using volatile. To achieve immutability, you simply need to mark x and y final.

As a side note and as already mentioned, synchronized(this) {} can be treated as a no-op by the JVM (I understand you included it to reproduce the behaviour).

这篇关于为什么这个Java程序终止,尽管显然它不应该(而没有)?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆