gst*_*low 8 java concurrency visibility volatile happens-before
众所周知,如果我们有一些对象引用并且此引用具有final字段 - 我们将看到来自final字段的所有可到达字段(至少在构造函数完成时)
class Foo{
private final Map map;
Foo(){
map = new HashMap();
map.put(1,"object");
}
public void bar(){
System.out.println(map.get(1));
}
}
Run Code Online (Sandbox Code Playgroud)
正如我在这种情况下所做的那样,我们保证bar()方法总是输出object因为:
1.我列出了完整的类代码,Foo并且map是final;
2.如果某个线程会看到引用Foo和引用!= null,那么我们保证从最终map引用值可以到达的是实际的.
我也是这么认为的
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));
}
}
Run Code Online (Sandbox Code Playgroud)
在这里,我们对bar()方法有相同的保证,但尽管在分配之前进行了 分配,但仍bar2可以抛出NullPointerExceptionnonFinalMapmap
我想知道volatile如何:
class Foo{
private volatile Map map;
Foo(){
map = new HashMap();
map.put(1,"object");
}
public void bar(){
System.out.println(map.get(1));
}
}
Run Code Online (Sandbox Code Playgroud)
据我所知,bar()方法不能扔,NullPoinerException但它可以打印null; (我完全不确定这个方面)
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));
}
}
Run Code Online (Sandbox Code Playgroud)
我认为这里我们有相同的保证bar()方法也bar2()不能抛出,NullPointerException因为nonVolatileMap赋值写入更高的volatile映射赋值但它可以输出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();
}
}
Run Code Online (Sandbox Code Playgroud)
请将我的评论改为或更正为代码片段.
Ale*_*lev 22
在当前Java内存模型的领域,volatile并不相同final.换句话说,你不能代替final用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"
Run Code Online (Sandbox Code Playgroud)
因此,volatile在构造函数中编写字段并不安全.
直觉:m上面的例子中有一场比赛.通过制造这个领域 并没有消除这场比赛M.x volatile,只会让m自己volatile有所帮助.换句话说,volatile该示例中的修饰符位于错误的位置以使其有用.在安全发布中,您必须具有"写入 - >易失性写入 - >易失性读取,观察易失性写入 - >读取(现在观察易失性写入之前的写入)",而是"易失性写入 - >写入 - >读取 - > volatile read(不遵守volatile写入)".
琐事1:这个属性意味着我们可以volatile在构造函数中更积极地优化-s.这证实了直觉,即未观察到的易失性存储(实际上直到具有非逃逸this完成的构造函数才能观察到)可以放松.
琐事2:这也意味着你无法安全地初始化volatile变量.更换M用AtomicInteger上面的例子,你有一个奇特的现实生活中的行为!new AtomicInteger(42)在一个线程中调用,不安全地发布实例,并get()在另一个线程中执行 - 你保证观察到42吗?如上所述,JMM说"不".Java内存模型的较新版本力求保证所有初始化的安全构建,以捕获此案例.而许多非重要的非x86端口已经加强了这个安全性.
琐事3: Doug Lea:"这个finalvs 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"
Run Code Online (Sandbox Code Playgroud)
如果在 volatile读取之后存在传递读取volatile字段,则观察由构造函数中的volatile写入的值,通常的安全发布规则启动.
| 归档时间: |
|
| 查看次数: |
829 次 |
| 最近记录: |