Arc*_*hie 11 java java-memory-model
我正在尝试解决一个简单的问题,并陷入Java内存模型兔子洞.
什么是最简单和/或最有效(判断调用此处),但无竞争(根据JMM精确定义)编写包含非最终引用字段的Java类的方法,该字段初始化为非空值构造函数,后来从未改变过,这样任何其他线程的后续访问都不能看到非空值?
破碎的起始示例:
public class Holder {
private Object value;
public Holder(Object value) {
if (value == null)
throw NullPointerException();
this.value = value;
}
public Object getValue() { // this could return null!
return this.value;
}
}
Run Code Online (Sandbox Code Playgroud)
而根据这篇文章,标记该领域volatile甚至不起作用!
public class Holder {
private volatile Object value;
public Holder(Object value) {
if (value == null)
throw NullPointerException();
this.value = value;
}
public Object getValue() { // this STILL could return null!!
return this.value;
}
}
Run Code Online (Sandbox Code Playgroud)
这是我们能做的最好的吗?
public class Holder {
private Object value;
public Holder(Object value) {
if (value == null)
throw NullPointerException();
synchronized (this) {
this.value = value;
}
}
public synchronized Object getValue() {
return this.value;
}
}
Run Code Online (Sandbox Code Playgroud)
好的,这个怎么样?
public class Holder {
private Object value;
public Holder(Object value) {
if (value == null)
throw NullPointerException();
this.value = value;
synchronized (this) { }
}
public synchronized Object getValue() {
return this.value;
}
}
Run Code Online (Sandbox Code Playgroud)
旁注:相关问题询问如何在不使用任何volatile或同步的情况下执行此操作,这当然是不可能的.
要在Java中安全地发布非不可变对象,您需要同步对象的构造以及对该对象的共享引用的写入.在这个问题中,重要的不仅仅是该对象的内部结构.
如果在没有正确同步的情况下发布对象,并且通过重新排序,Holder如果在构造函数完成之前发布了对象的引用,则对象的使用者仍然可以看到部分构造的对象.例如,双重检查锁定没有volatile.
有几种方法可以安全地发布对象:
volatile字段或AtomicReference请注意,这些要点是讨论Holder对象的引用,而不是类的字段.
所以最简单的方法是第一个选择:
public static Holder holder = new Holder("Some value");
Run Code Online (Sandbox Code Playgroud)
访问静态字段的任何线程都将看到正确构造的Holder对象.
请参见实践中Java Concurrency的第3.5.3节"安全发布惯用法" .有关不安全发布的更多信息,请参见" 实践中的Java并发"一节中的第16.2.1节.
您尝试解决的问题称为安全发布,并且存在最佳性能解决方案的基准.就个人而言,我更喜欢持久性模式,它也表现最佳.Publisher使用单个通用字段定义类:
class Publisher<T> {
private final T value;
private Publisher(T value) { this.value = value; }
public static <S> S publish(S value) { return new Publisher<S>(value).value; }
}
Run Code Online (Sandbox Code Playgroud)
您现在可以通过以下方式创建实例:
Holder holder = Publisher.publish(new Holder(value));
Run Code Online (Sandbox Code Playgroud)
由于您Holder通过final字段取消引用,因此在从相同的最终字段读取后,JMM保证完全初始化.
如果这是您班级的唯一用法,那么您当然应该为您的班级添加一个便利工厂,并使构造函数本身private避免不安全的构造.
请注意,这很好,因为现代VM在应用转义分析后会擦除对象分配.最小的性能开销来自生成的机器代码中的剩余内存障碍,但是安全地发布实例需要这些障碍.
注意:不要将持有者模式与被调用的示例类混淆Holder.它是Publisher在我的例子中实现持有者模式.
| 归档时间: |
|
| 查看次数: |
541 次 |
| 最近记录: |