Java 双重检查锁定,无易失性
我读过关于如何进行双重检查锁定的内容:Java 双重检查锁定,无易失性,java,multithreading,final,java-memory-model,double-checked-locking,Java,Multithreading,Final,Java Memory Model,Double Checked Locking,我读过关于如何进行双重检查锁定的内容: // Double-check idiom for lazy initialization of instance fields private volatile FieldType field; FieldType getField() { FieldType result = field; if (result == null) { // First check (no locking) synchronized(thi
// Double-check idiom for lazy initialization of instance fields
private volatile FieldType field;
FieldType getField() {
FieldType result = field;
if (result == null) { // First check (no locking)
synchronized(this) {
result = field;
if (result == null) // Second check (with locking)
field = result = computeFieldValue();
}
}
return result;
}
我的目标是在没有volatile属性的情况下懒洋洋地加载一个字段(不是单例)。初始化后,字段对象永远不会更改
在对我的最终方法进行一些测试后:
private FieldType field;
FieldType getField() {
if (field == null) {
synchronized(this) {
if (field == null)
field = Publisher.publish(computeFieldValue());
}
}
return fieldHolder.field;
}
public class Publisher {
public static <T> T publish(T val){
return new Publish<T>(val).get();
}
private static class Publish<T>{
private final T val;
public Publish(T val) {
this.val = val;
}
public T get(){
return val;
}
}
}
使用Java8进行测试,但至少应使用Java6+
但我想知道这是否有效:
// Double-check idiom for lazy initialization of instance fields without volatile
private FieldHolder fieldHolder = null;
private static class FieldHolder{
public final FieldType field;
FieldHolder(){
field = computeFieldValue();
}
}
FieldType getField() {
if (fieldHolder == null) { // First check (no locking)
synchronized(this) {
if (fieldHolder == null) // Second check (with locking)
fieldHolder = new FieldHolder();
}
}
return fieldHolder.field;
}
甚至可能:
// Double-check idiom for lazy initialization of instance fields without volatile
private FieldType field = null;
private static class FieldHolder{
public final FieldType field;
FieldHolder(){
field = computeFieldValue();
}
}
FieldType getField() {
if (field == null) { // First check (no locking)
synchronized(this) {
if (field == null) // Second check (with locking)
field = new FieldHolder().field;
}
}
return field;
}
或:
我相信这将在以下基础上起作用:
final字段的使用模型很简单:在对象的构造函数中为对象设置final字段;并且在对象的构造函数完成之前,不要在另一个线程可以看到的地方编写对正在构造的对象的引用。如果紧跟其后,那么当另一个线程看到该对象时,该线程将始终看到该对象最终字段的正确构造版本。它还将看到最终字段引用的任何对象或数组的版本,这些版本至少与最终字段一样最新
不,这样不行
final
不能保证volatile所能保证的线程之间的可见性。您引用的Oracle文档表示,其他线程将始终看到对象最终字段的正确构造版本final
保证在对象构造函数完成运行时,所有final字段都已构造和设置。因此,如果对象Foo
包含最后一个字段bar
,bar
保证在Foo
的构造函数完成时被构造
但是,final
字段引用的对象仍然是可变的,对该对象的写入可能无法在不同线程中正确显示
因此,在您的示例中,不能保证其他线程看到已创建并可能创建另一个的FieldHolder
对象,或者如果FieldType
对象的状态发生任何修改,也不能保证其他线程会看到这些修改。final
关键字只保证一旦其他线程看到了FieldType
对象,就会调用其构造函数。引用@Kicsi提到的内容,最后一部分是:
双重检查锁定不可变对象
如果Helper是不可变对象,则
辅助对象是最终的,然后双重检查锁定将在没有
要使用易失性字段。其思想是引用一个不可变的
对象(如字符串或整数)的行为应该大致相同
作为int或float的方式;读写对不可变对象的引用
对象是原子的
(重点是我的)
由于FieldHolder
是不可变的,您确实不需要volatile
关键字:其他线程将始终看到正确初始化的FieldHolder
。据我所知,字段类型
在通过FieldHolder
从其他线程访问之前总是会被初始化
但是,如果FieldType
不是不可变的,则仍然需要进行适当的同步。因此,我不确定您是否会从避免使用volatile
关键字中获益匪浅
如果它是不可变的,那么按照上面的引文,您根本不需要
字段持有者。第一件事:您试图做的事情充其量是危险的。当人们试图在期末考试中作弊时,我有点紧张。Java语言为您提供了volatile
作为处理线程间一致性的工具。使用它
无论如何,相关方法如中所述
作为:
按照外行的说法,它是这样工作的<当我们观察到wrapper
为null时,code>synchronized
会产生正确的同步——换句话说,如果我们完全放弃第一个检查并将synchronized
扩展到整个方法体,那么代码显然是正确的<在FinalWrapper
中,code>final保证如果我们看到非空的wrapper
,它是完全构造的,并且所有单例
字段都是可见的——这将从wrapper
的快速读取中恢复
请注意,它在字段中传递的是FinalWrapper
,而不是值本身。如果instance
在没有FinalWrapper
的情况下发布,所有赌注都将被取消(用外行的话说,这是过早发布)。这就是为什么您的Publisher.publish
不起作用的原因:只是将值放在final字段中,读回,然后不安全地发布它是不安全的——这与只将裸实例写出来非常相似
此外,当您发现空的包装器
,并使用其值时,必须小心地在锁下读取“回退”。在return语句中第二次(第三次)读取wrapper
,也会破坏正确性,使您处于合法的竞争状态
编辑:顺便说一句,如果您发布的对象内部覆盖了final
-s,那么您可以剪切FinalWrapper
的中间人,并发布实例本身
编辑2:另请参见,以及评论中的一些讨论。简而言之
没有volatile或wrapper类的代码版本取决于JVM运行的底层操作系统的内存模型
带有包装类的版本是一种已知的替代方案,称为设计模式,它依赖于类加载器
契约,即任何给定类在第一次访问时最多加载一次,并且以线程安全的方式加载
需要volatile
开发人员对代码执行的看法主要是
// Double-check idiom for lazy initialization of instance fields without volatile
private FieldType field = null;
private static class FieldHolder{
public final FieldType field;
FieldHolder(){
field = computeFieldValue();
}
}
FieldType getField() {
if (field == null) { // First check (no locking)
synchronized(this) {
if (field == null) // Second check (with locking)
field = new FieldHolder().field;
}
}
return field;
}
// Double-check idiom for lazy initialization of instance fields without volatile
private FieldType field = null;
FieldType getField() {
if (field == null) { // First check (no locking)
synchronized(this) {
if (field == null) // Second check (with locking)
field = new Object(){
public final FieldType field = computeFieldValue();
}.field;
}
}
return field;
}
public class FinalWrapperFactory {
private FinalWrapper wrapper;
public Singleton get() {
FinalWrapper w = wrapper;
if (w == null) { // check 1
synchronized(this) {
w = wrapper;
if (w == null) { // check2
w = new FinalWrapper(new Singleton());
wrapper = w;
}
}
}
return w.instance;
}
private static class FinalWrapper {
public final Singleton instance;
public FinalWrapper(Singleton instance) {
this.instance = instance;
}
}
}
public enum EnumSingleton {
/**
* using enum indeed avoid reflection intruding but also limit the ability of the instance;
*/
INSTANCE;
SingletonTypeEnum getType() {
return SingletonTypeEnum.ENUM;
}
}
/**
* Singleton:
* The JLS guarantees that a class is only loaded when it's used for the first time
* (making the singleton initialization lazy)
*
* Thread-safe:
* class loading is thread-safe (making the getInstance() method thread-safe as well)
*
*/
private static class SingletonHelper {
private static final LazyInitializedSingleton INSTANCE = new LazyInitializedSingleton();
}
class Foo {
private volatile Helper helper = null;
public Helper getHelper() {
if (helper == null) {
synchronized(this) {
if (helper == null)
helper = new Helper();
}
}
return helper;
}
}