我最近遇到了一个有趣的问题:
例如我有A类:
class A {
int a;
int b;
public A() {
a = 1;
b = 2;
}
public int getA() {return a;}
public int getB() {return b;}
}
A类必须仅作为单身人士存在; 因此,为了提供对单例的访问,我创建了类Factory:
class Factory {
private A a;
public A getA() {
if (a == null) {
synchronized(this) {
if (a == null) {
a = new A();
}
}
}
return a;
}
}
据我了解,根据JVM中的重新排序,如果2个线程同时访问Factory.getA(),则其中一个线程可能会获得部分构造的对象,并且可能导致应用程序崩溃。 / p>
但如果我私有A a; volatile我可以确定每个线程只能访问完全构造的对象吗?
因此,作为结论,如果我将变量x标记为volatile,它是否会影响x.class构造函数的内容?
答案 0 :(得分:2)
但如果我私有A a; volatile我可以确定每个线程只能访问完全构造的对象吗?
是
考虑线程#1初始化a
的情况。工厂互斥锁确保一次只有一个线程可以创建A
。所以我们有以下顺序:
线程#1看到a
为空
线程#1获取互斥锁。
主题#1见a
仍为空。
线程#1构造一个A
实例,用于初始化其变量。
线程#1将实例引用分配给a
。
线程#1释放锁定。
对于线程#2,看到a
处于非空状态(例如,在步骤#1或步骤#3测试中),这意味着线程#1必须已经(至少)第五步。但这意味着
A.a = 1; // thread #1
"之前发生"
A.b = 2; // thread #1
"在"之前发生
write to Factory.a // thread #1
"发生在"
之前read from Factory.a // thread #2
"发生在"
之前access to some field of `A` // thread #2
因为在"之前有一个完整的"从线程#1中的新A
的字段初始化到线程#2中的字段访问的关系。这意味着线程#2 保证以查看字段初始化写入的结果...除非有其他干预写入这些字段。
请注意,"发生在"之前。写入易失性a
和后续读取之间的另一个线程是关键。如果a
不是易变的,那么在"之前就不会发生完整的"链和因此执行不是"发生 - 在一致之前&#34 ;;即它包含"数据竞赛"。
我上面使用/遵循的术语和推理来自Java语言规范,Chapter 17.4。
答案 1 :(得分:1)
<强>挥发性
Java volatile关键字用于将Java变量标记为“存储在主存储器中”。更确切地说,这意味着,每次读取一个volatile变量都将从计算机的主内存中读取,而不是从CPU缓存中读取,并且每次写入volatile变量都将写入主内存,而不仅仅是CPU缓存。
它与记忆有关。它不会影响构造函数。
来源:点击here
答案 2 :(得分:0)
因为你想让A成为一个单身人士,我的建议就是让A成为一个枚举。
使用枚举:
并且使用枚举是实现单例模式的最佳方式。
enum A{
INSTANCE(1,2);
private int a;
private int b;
A(int a,int b){
this.a=a;
this.b=b;
}
public int getA(){ return a; }
public int getB(){ return b; }
}
答案 3 :(得分:0)
规范和实现有两种不同的东西。如果你使a
变为volatile,那将解决实际问题,因为JVM会在赋值后插入商店障碍。但是这仍然会破坏规范,因为volatile
不像final
,并不能保证会发布对象的所有子对象。顺便说一句,在具有强大内存模型的CPU上,很多不良代码都可以正常工作,但这并不意味着你应该这样写。