Aud*_*rey 10 java lazy-loading thread-safety
当我阅读维基百科的关于Double Checked Locking成语的文章时,我对它的实现感到困惑:
public class FinalWrapper<T> {
public final T value;
public FinalWrapper(T value) {
this.value = value;
}
}
public class Foo {
private FinalWrapper<Helper> helperWrapper = null;
public Helper getHelper() {
FinalWrapper<Helper> wrapper = helperWrapper;
if (wrapper == null) {
synchronized(this) {
if (helperWrapper == null) {
helperWrapper = new FinalWrapper<Helper>(new Helper());
}
wrapper = helperWrapper;
}
}
return wrapper.value;
}
}
Run Code Online (Sandbox Code Playgroud)
我根本不明白为什么我们需要创建包装器.这不够吗?
if (helperWrapper == null) {
synchronized(this) {
if (helperWrapper == null) {
helperWrapper = new FinalWrapper<Helper>(new Helper());
}
}
}
Run Code Online (Sandbox Code Playgroud)
是因为使用包装器可以加速初始化,因为包装器存储在堆栈上并且helperWrapper存储在堆中?
这还不够吗?
Run Code Online (Sandbox Code Playgroud)if (helperWrapper == null) { synchronized(this) { if (helperWrapper == null) { helperWrapper = new FinalWrapper<Helper>(new Helper()); } } }
不,这还不够。
上面,首先检查是否helperWrapper == null不是线程安全的。对于某些线程“太早”,它可能返回 false(看到非空实例),指向未完全构造的 helperWrapper 对象。
您引用的维基百科文章逐步解释了这个问题:
例如,考虑以下事件序列:
- 线程A注意到该值没有初始化,因此它获得锁并开始初始化该值。
- 由于某些编程语言的语义,允许编译器生成的代码在 A 完成初始化之前更新共享变量以指向部分构造的对象。
- 线程 B 注意到共享变量已被初始化(或者看起来如此),并返回其值。因为线程 B 认为该值已经初始化,所以它不会获取锁。如果 B 在 A 完成的所有初始化被 B 看到之前使用该对象(因为 A 尚未完成初始化,或者因为对象中的某些初始化值尚未渗透到 B 使用的内存(缓存一致性)) ,程序可能会崩溃。
注意,上面提到的一些编程语言的语义与 Java 1.5 及更高版本的语义完全相同。Java 内存模型 (JSR-133) 明确允许此类行为 - 如果您感兴趣,请在网络上搜索更多详细信息。
是不是因为使用wrapper可以加快初始化速度,因为wrapper存储在栈上,而helperWrapper存储在堆上?
不,上面不是原因。
原因是线程安全。同样,Java 1.5 及更高版本的语义(如 Java 内存模型中定义)保证任何线程都只能从包装器访问正确初始化的 Helper 实例,因为它是在构造函数中初始化的最终字段 - 请参阅JLS 17.5 Final字段语义。
| 归档时间: |
|
| 查看次数: |
422 次 |
| 最近记录: |