gpt4 book ai didi

java - volatile 发布保证有多深?

转载 作者:塔克拉玛干 更新时间:2023-11-03 03:29:48 25 4
gpt4 key购买 nike

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

例子1:

class Foo{
private final Map map;
Foo(){
map = new HashMap();
map.put(1,"object");
}

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

据我了解,在这种情况下,我们可以保证 bar() 方法始终输出 object,因为:
1. 我列出了 Foo 类的完整代码, map 是最终的;
<强>2。如果某个线程将看到 Foo 的引用并且此引用 != null,那么我们可以保证从最终 map 引用值可达的值将是实际的。。

我也觉得

例子2:

class Foo {
private final Map map;
private Map nonFinalMap;

Foo() {
nonFinalMap = new HashMap();
nonFinalMap.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));
}
}

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

我想知道 volatile 怎么样:

示例 3:

class Foo{
private volatile Map map;
Foo(){
map = new HashMap();
map.put(1,"object");
}

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

据我所知,bar() 方法不能抛出 NullPoinerException 但它可以打印 null; (我完全不确定这方面)

示例 4:

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

我认为这里我们对 bar() 方法有相同的保证 bar2() 不能抛出 NullPointerException 因为 nonVolatileMap assignment 写了更高的 volatile map assignment 但它可以输出 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。换句话说,you cannot replace final with volatile ,并认为安全施工保证是一样的。值得注意的是,这在理论上是可以发生的:

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 上有一场比赛。通过使 field M.x volatile 不会消除该竞争,只会使 m 本身 volatile 会有所帮助。换句话说,该示例中的 volatile 修饰符用在了错误的地方。在安全发布中,您必须具有“写入 -> volatile 写入 -> 观察 volatile 写入 -> 读取的 volatile 读取(现在在 volatile 写入之前观察写入)”,而是具有“ volatile 写入 -> 写入 -> 读取 -> volatile 读(不观察 volatile 写)”。

琐事 1: 这个属性意味着我们可以在构造函数中更积极地优化 volatile-s。这证实了可以放宽未观察到的 volatile 存储(实际上直到具有非转义 this 的构造函数完成后才观察到)的直觉。

知识点 2: 这也意味着您无法安全地初始化 volatile 变量。将上面示例中的 M 替换为 AtomicInteger,您将获得一种奇特的真实行为!在一个线程中调用 new AtomicInteger(42),不安全地发布实例,然后在另一个线程中执行 get() - 你保证观察到 42?如前所述,JMM 说“不”。 Java 内存模型的较新修订版努力保证所有初始化的安全构造,以捕获这种情况。还有许多重要的非 x86 端口 have already strengthened this为了安全。

知识问答 3: Doug Lea :“这个 final vs 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 字段 volatile read 观察到构造函数中的 volatile write 写入的值后有传递读取,通常的安全发布规则就会生效。

关于java - volatile 发布保证有多深?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/42012658/

25 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com