有人能验证我对构造函数执行后建立的内存围栏的理解吗。例如,假设我有一个名为Stock的类。
public final class Stock{
private final String ticker;
private double qty;
private double price;
public Stock ( String ticker, double qty, double price ){
this.ticker = ticker;
this.qty = qty;
this.price = price;
//I am assuming a memory fence gets inserted here.
}
public final void updateQty( double qty ){
this.qty = qty;
}
public final void updatePrice( double price ){
this.price = price;
}
}
此外,假设构造函数由Thread1执行,然后updateQty()
和updatePrice()
由Thread2多次调用(始终由Thread2调用)。
我的论点是,在Thread1创建对象之后,对象的"可见性"与jvm中的所有其他线程一起建立。由于这两个可变变量只由Thread2更改,所以我不需要任何锁定。我说得对吗?
我的争论是,在Thread1创建对象后,对象的"可见性"将与jvm中的所有其他线程建立起来。
这是不正确的。没有隐含的构造函数内存屏障,这就是为什么围绕构造函数重新排序指令是一个问题。如果要在构建对象的线程之外的另一个线程中使用Stock
对象,则在调用任何更新方法之前,必须对该对象使用synchronize
。
由于这两个可变变量只由Thread2更改,所以我不需要任何锁定。
在Thread2
中对对象进行初始同步后,您将不需要任何额外的锁定,除非您希望在其他线程中看到那些突变的字段。如果多个线程正在读取Stock
对象,而Thread2
正在对其进行更改,则所有线程都需要通过同步或使字段变为volatile
来跨越内存屏障。
这与构造函数操作重新排序和内存可见性有关。有关构造函数重新排序的更多陷阱,请参阅以下答案:
这是对象的安全发布吗?
不幸的是,没有。构造函数(主要)像Java内存模型中的一个普通方法。
不过,这很糟糕,它给程序员带来了无尽的困惑。