Java 有多深?
众所周知,如果我们有一些对象引用,并且这个引用有final字段,我们将看到final字段中所有可访问的字段(至少在构造函数完成时) 例1: 正如我所理解的,在这种情况下,我们可以保证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
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"