Java 引用类型的Volatile-它是否总是避免由于JMM而发布引用问题?
假设这一类:Java 引用类型的Volatile-它是否总是避免由于JMM而发布引用问题?,java,multithreading,concurrency,thread-safety,volatile,Java,Multithreading,Concurrency,Thread Safety,Volatile,假设这一类: public class AmIThreadSafe { private int a; private int b; AmIThreadSafe(int a, int b) { this.a = a; this.b = b; } } 假设只要this(引用)转义,一些线程就可以访问该实例对此类的引用(声明为volatile)(导致争用条件): 在这里,我确信分配实例引用的事实发生在线程读取之前 但是AmIThre
public class AmIThreadSafe {
private int a;
private int b;
AmIThreadSafe(int a, int b) {
this.a = a;
this.b = b;
}
}
假设只要this
(引用)转义,一些线程就可以访问该实例对此类的引用(声明为volatile
)(导致争用条件):
在这里,我确信分配实例
引用的事实发生在线程读取之前
但是AmIThreadSafe的
字段呢
外部volatile
关键字是否也意味着发生在a
和b
字段的关系之前?
或者,由于构造函数期间可能的语句重新排序,是否有可能最终导致任何线程看到过时的值(在本例中是默认值0
,因为int
)
换句话说,我应该声明a
和b
final
还是volatile
,以防止JMM出现任何意外情况,还是仅仅在实例的引用上指示volatile
----------------更新帖子-一个好答案:----------------------------
下面的文章通过它的示例证实,在我的例子中,a
和b
受到JMM优化的保护,JMM优化可以防止永久的发生在关系之前
不这还不足以让它变得不稳定。线程安全性取决于使用情况。例如,如果另一个线程正在修改值,这仍然可能产生意外的结果
为简单起见,假设public
变量
volatile
仅适用于变量(例如,x
和y
不会自动volatile
仅仅因为实例
是可变的)。这一点应该从中明确,您的案例中的volatile
仅适用于AmlThreadSafe
的引用。您仍然必须使实例变量(a
和b
)volatile
或在同步
块中访问它们。否则,您可能会得到过时的数据。如果a
和b
仅在构造函数中修改,那么在这种情况下,您应该没有问题,因为对象是在引用分配给实例之前创建的(以及a
和b
集),任何其他线程在这些位置都不会有本地缓存的内存副本,因为它是线程以前无法看到的新对象。换句话说,我认为另一个线程不可能看到a
和b
的“默认”值0,因为构造函数将在对象的引用分配给实例之前完全运行
但是,如果可以在构造函数之后修改a
和b
,则此处的其他答案是正确的-您需要围绕它们进行同步
如果你要假设a
和b
不会在构造函数之外被修改,那么没有理由不将它们设置为最终值,只是为了安全。将实例
声明为volatile
不会使其字段volatile
,但是如果我正确理解你的问题,那么-是的,对你来说就足够了
Per:
- 一个线程中的
volatile
写入发生在另一个线程中的任何后续volatile
读取之前
- 同一线程中的语句具有预期的“发生在之前”关系
- 发生在关系可传递之前
因此,如果一个线程认为实例
已经初始化,那么实例
的初始化就发生在它之前,而实例
字段的初始化就发生在它之前,因此线程会认为实例
的字段已经初始化。是
thread 1 thread 2
1 write(a)
2 write(instance)
3 read(instance)
4 read(a)
由于实例是可变的,[2]发生在[3]之前
由于before是可传递的,我们有hb(1,2),hb(2,3),hb(3,4),因此hb(1,4)示例:
class Something{
private volatile static Something instance = null;
private int x;
private int y;
private Something(){
this.x = 1;
this.y = 2;
}
public static Something getInstance() {
if (instance == null) {
synchronized (Something.class) {
if (instance == null)
instance = new Something();
}
}
}
return instance;
}
}
说明:
假设我们有上面的代码:
现在让我们假设实例在一段时间内不易波动:
线程#1:
在invoke getInstance方法中,检查实例值{since null},将进入IF条件,访问锁现在再次找到实例==null,调用某个构造函数。现在进入构造函数体内部
一旦线程#1进入构造函数体,就会发生上下文切换,现在轮到线程#2执行
线程#2:
调用get Instance,但突然发现该实例不是null?为什么{Reason将在此之后讨论}并因此将部分构造的对象分配给引用并返回它
现在的情况是这样的:线程#1仍然需要完全构造对象{需要完全构造它},线程#2获得了部分构造对象的引用,如果它像say reference那样使用它,x//将打印“x”默认值而不是“1”
为什么在线程#2的情况下返回部分构造的对象引用?
原因很简单:语句重新排序。
对象创建和引用关联的步骤很简单:
在堆中分配内存
执行将初始化类成员的构造函数体
完成上述步骤后,引用新创建的对象
但有时编译器会无序执行这些指令,这意味着:
可能会发生这样的事情:
在堆中分配内存
对新创建的对象的引用
执行将初始化类成员的构造函数体
一旦发生上述两个步骤,如果发生上下文切换
thread 1 thread 2
1 write(a)
2 write(instance)
3 read(instance)
4 read(a)
class Something{
private volatile static Something instance = null;
private int x;
private int y;
private Something(){
this.x = 1;
this.y = 2;
}
public static Something getInstance() {
if (instance == null) {
synchronized (Something.class) {
if (instance == null)
instance = new Something();
}
}
}
return instance;
}
}