Java 有多深?

Java 有多深?,java,concurrency,visibility,volatile,happens-before,Java,Concurrency,Visibility,Volatile,Happens Before,众所周知,如果我们有一些对象引用,并且这个引用有final字段,我们将看到final字段中所有可访问的字段(至少在构造函数完成时) 例1: 正如我所理解的,在这种情况下,我们可以保证bar()方法总是输出object,因为: 1.我列出了类Foo的完整代码,地图是最终的 2。如果某个线程将看到Foo的引用和此引用!=null,那么我们保证从最终的映射参考值将是实际的 我也认为 例2: 这里我们对bar()方法有相同的保证,但是bar2可以抛出NullPointerException,尽管nonF

众所周知,如果我们有一些对象引用,并且这个引用有final字段,我们将看到final字段中所有可访问的字段(至少在构造函数完成时)

例1: 正如我所理解的,在这种情况下,我们可以保证
bar()
方法总是输出
object
,因为:
1.我列出了类
Foo
的完整代码,地图是最终的
2。如果某个线程将看到
Foo的引用和此引用!=null,那么我们保证从最终的
映射
参考值将是实际的

我也认为

例2: 这里我们对
bar()
方法有相同的保证,但是
bar2
可以抛出
NullPointerException
,尽管
nonFinalMap
赋值发生在
map
赋值之前

我想知道volatile的情况:

例3: 据我所知,
bar()
方法不能抛出
NullPoinerException
,但它可以打印
null
;(我对这方面完全没有把握)

例4: 我想在这里我们对
bar()
方法也有同样的保证
bar2()
不能抛出
NullPointerException
,因为
非易失性映射
赋值写的是更高的易失性映射赋值,但它可以输出null


在Elliott Frisch评论之后添加了

通过种族示例发布:

public class Main {
    private static Foo foo;

    public static void main(String[] args) {
        new Thread(new Runnable() {
            @Override
            public void run() {
                foo = new Foo();
            }
        }).start();


        new Thread(new Runnable() {
            @Override
            public void run() {
                while (foo == null) ; // empty loop

                foo.bar();
            }
        }).start();

    }
}

请检查或更正我对代码片段的评论。

在当前Java内存模型中,
volatile
不等于
final
。换句话说,我认为安全施工保证是一样的。值得注意的是,这在理论上可能发生:

public class M {
  volatile int x;
  M(int v) { this.x = v; }
  int x() { return x; }
}

// thread 1
m = new M(42);

// thread 2
M lm;
while ((lm = m) == null); // wait for it
print(lm.x()); // allowed to print "0"
因此,在构造函数中编写
volatile
字段并不安全

直觉:在上面的例子中,
m
上有一场比赛。使字段
M.x
volatile
不会消除这种竞争,只有使
M
本身
volatile
才有帮助。换句话说,在那个例子中,
volatile
修饰符位于错误的有用位置。在安全发布中,您必须具有“写入->易失性写入->观察易失性写入->读取的易失性读取(现在观察易失性写入之前的写入)”,而您必须具有“易失性写入->写入->读取->易失性读取(不观察易失性写入)”

琐事1:此属性意味着我们可以在构造函数中更积极地优化
volatile
-s。这证实了一种直觉,即未观察到的易失性存储(事实上,在使用非转义
This
完成的构造函数之前,它不会被观察到)可以被放松

琐事2:这也意味着您无法安全地初始化
volatile
变量。在上面的示例中,将
M
替换为
AtomicInteger
,您就有了一种特殊的现实行为!在一个线程中调用
new AtomicInteger(42)
,不安全地发布实例,并在另一个线程中执行
get()
——是否保证遵守
42
?如前所述,JMM表示“不”。Java内存模型的较新版本力求保证所有初始化的安全构造,以捕获这种情况。以及许多非x86端口,这对安全至关重要

琐事3::“这个
最终版
volatile
问题导致java.util.concurrent中出现了一些扭曲的构造,允许0作为基本值/默认值(如果不是自然值)。这个规则很糟糕,应该更改。”

也就是说,这个例子可以变得更加狡猾:

public class C {
  int v;
  C(int v) { this.x = v; }
  int x() { return x; }    
}

public class M {
  volatile C c;
  M(int v) { this.c = new C(v); }
  int x() { 
    while (c == null); // wait!
    return c.x();
  }
}

// thread 1
m = new M(42);

// thread 2
M lm;
while ((lm = m) == null); // wait for it
print(lm.x()); // always prints "42"

如果在volatile read观察到volatile write-in构造函数写入的值之后,有一个可传递的read-through
volatile
字段,则通常的安全发布规则生效。

没有调用方可以访问
映射
(并且没有方法写入
映射
)所以我不明白你为什么关心线程。@Elliott Frisch你在讨论哪个例子?另请阅读以下内容:和@user889742谢谢,已修复。看起来我是tired@Elliott弗里希:我添加了种族宣传的例子,这对我来说几乎是显而易见的。但是我没有抓住琐事2,在一个线程中调用新的AtomicInteger(42)
,不安全地发布它,并在另一个线程中执行
get()
,如果使用AtomicInteger,您保证遵守
42
?如前所述,JMM说“不”。因为AtomicInteger只是在volatile int上进行包装?是的。请参见示例中
AtomicInteger
M
之间的对称性。@PetrJaneček:“让我困惑的是M变量——如果它不是易失性的,while循环可能会永远循环。”哦,OPs示例有一个循环,我尝试匹配它。但这实际上是一个可教的时刻:编译器可以将非易失性循环简化为无限循环,这并不意味着他们应该这样做。JMM只说明了在该示例中兼容实现可以输出的结果:对于非易失性循环,它说{nothing,0,42}。
class Foo {
    private volatile Map map;
    private Map nonVolatileMap;

    Foo() {
        nonVolatileMap= new HashMap();
        nonVolatileMap.put(2, "ololo");
        map = new HashMap();
        map.put(1, "object");
    }

    public void bar() {
        System.out.println(map.get(1));
    }

    public void bar2() {
        System.out.println(nonFinalMap.get(2));
    }
}
public class Main {
    private static Foo foo;

    public static void main(String[] args) {
        new Thread(new Runnable() {
            @Override
            public void run() {
                foo = new Foo();
            }
        }).start();


        new Thread(new Runnable() {
            @Override
            public void run() {
                while (foo == null) ; // empty loop

                foo.bar();
            }
        }).start();

    }
}
public class M {
  volatile int x;
  M(int v) { this.x = v; }
  int x() { return x; }
}

// thread 1
m = new M(42);

// thread 2
M lm;
while ((lm = m) == null); // wait for it
print(lm.x()); // allowed to print "0"
public class C {
  int v;
  C(int v) { this.x = v; }
  int x() { return x; }    
}

public class M {
  volatile C c;
  M(int v) { this.c = new C(v); }
  int x() { 
    while (c == null); // wait!
    return c.x();
  }
}

// thread 1
m = new M(42);

// thread 2
M lm;
while ((lm = m) == null); // wait for it
print(lm.x()); // always prints "42"