编程语言
首页 > 编程语言> > java – 没有volatile的懒惰初始化/ memoization

java – 没有volatile的懒惰初始化/ memoization

作者:互联网

看起来Java内存模型没有定义本地缓存的“刷新”和“刷新”,相反人们只是为了简单而这样称呼它,但实际上“发生在之前”的关系意味着以某种方式刷新和刷新(如果你可以解释一下,但不是问题的直接部分).

这让我非常困惑,因为关于Java Memory Model in the JLS的部分不是以易于理解的方式编写的.

因此,请您告诉我,我在以下代码中做出的假设是否正确,是否保证正确运行?

它部分基于Double-checked locking维基百科文章中提供的代码,但作者使用了包装类(FinalWrapper),但其原因并不完全明显.也许支持空值?

public class Memoized<T> {
    private T value;
    private volatile boolean _volatile;
    private final Supplier<T> supplier;

    public Memoized(Supplier<T> supplier) {
        this.supplier = supplier;
    }

    public T get() {
        /* Apparently have to use local variable here, otherwise return might use older value
         * see https://jeremymanson.blogspot.com/2008/12/benign-data-races-in-java.html
         */
        T tempValue = value;

        if (tempValue == null) {
            // Refresh
            if (_volatile);
            tempValue = value;

            if (tempValue == null) {
                // Entering refreshes, or have to use `if (_volatile)` again?
                synchronized (this) {
                    tempValue = value;

                    if (tempValue == null) {
                        value = tempValue = supplier.get();
                    }

                    /* 
                     * Exit should flush changes
                     * "Flushing" does not actually exists, maybe have to use  
                     * `_volatile = true` instead to establish happens-before?
                     */
                }
            }
        }

        return tempValue;
    }
}

我还读过构造函数调用可以内联并重新排序,从而导致对未初始化对象的引用(参见this comment on a blog).直接分配供应商的结果是否安全,或者必须分两步完成?

value = tempValue = supplier.get();

两个步骤:

tempValue = supplier.get();
// Reorder barrier, maybe not needed?
if (_volatile);
value = tempValue;

编辑:这个问题的标题有点误导,目标是减少挥发性字段的使用.如果初始化值已经在线程的高速缓存中,则直接访问值而无需再次查看主存储器.

解决方法:

如果你只有几个单身,你可以减少挥发性的使用.注意:您必须为每个单例重复此代码.

enum LazyX {
   ;
   static volatile Supplier<X> xSupplier; // set somewhere before use

   static class Holder {
       static final X x = xSupplier.get();
   }

   public static X get() {
       return Holder.x;
   }
}

如果您了解供应商,这将变得更加简单

enum LazyXpensive {
   ;

   // called only once in a thread safe manner
   static final Xpensive x = new Xpensive();

   // after class initialisation, this is a non volatile read
   public static Xpensive get() {
       return x;
   }
}

您可以使用Unsafe避免使字段变得不稳定

import sun.misc.Unsafe;

import java.lang.reflect.Field;
import java.util.function.Supplier;

public class LazyHolder<T> {
    static final Unsafe unsafe = getUnsafe();
    static final long valueOffset = getValueOffset();

    Supplier<T> supplier;
    T value;

    public T get() {
        T value = this.value;
        if (value != null) return value;

        return getOrCreate();
    }

    private T getOrCreate() {
        T value;
        value = (T) unsafe.getObjectVolatile(this, valueOffset);
        if (value != null) return value;

        synchronized (this) {
            value = this.value;
            if (value != null) return value;
            this.value = supplier.get();
            supplier = null;
            return this.value;
        }
    }


    public static Unsafe getUnsafe() {
        try {
            Field theUnsafe = Unsafe.class.getDeclaredField("theUnsafe");
            theUnsafe.setAccessible(true);
            return (Unsafe) theUnsafe.get(null);
        } catch (NoSuchFieldException | IllegalAccessException e) {
            throw new AssertionError(e);
        }
    }

    private static long getValueOffset() {
        try {
            return unsafe.objectFieldOffset(LazyHolder.class.getDeclaredField("value"));
        } catch (NoSuchFieldException e) {
            throw new AssertionError(e);
        }
    }
}

但是,额外查找是微优化.如果您愿意为每个线程执行一次同步命中,则可以完全避免使用volatile.

标签:java,memoization,lazy-initialization,java-memory-model
来源: https://codeday.me/bug/20190622/1261366.html