java中延迟线程安全单例实例化的模式
懒惰的线程安全单例实例对每个编码器来说都不容易理解,所以我想在我们的企业框架中创建一个可以完成工作的类。
你怎么看待这件事? 你觉得它有什么坏处吗? 在Apache Commons中有类似的东西吗? 我怎样才能让它变得更好?
Supplier.java
public interface Supplier { public T get(); }
LazyThreadSafeInstantiator.java
public class LazyThreadSafeInstantiator implements Supplier { private final Supplier instanceSupplier; private volatile T obj; public LazyThreadSafeInstantiator(Supplier instanceSupplier) { this.instanceSupplier = instanceSupplier; } @Override // http://en.wikipedia.org/wiki/Double-checked_locking public T get() { T result = obj; // Wikipedia: Note the usage of the local variable result which seems unnecessary. For some versions of the Java VM, it will make the code 25% faster and for others, it won't hurt. if (result == null) { synchronized(this) { result = obj; if (result == null) { result = instanceSupplier.get(); obj = result; } } } return result; } }
用法示例:
public class Singleton1 { private static final Supplier instanceHolder = new LazyThreadSafeInstantiator(new Supplier() { @Override public Singleton1 get() { return new Singleton1(); } }); public Singleton1 instance() { return instanceHolder.get(); } private Singleton1() { System.out.println("Singleton1 instantiated"); } }
谢谢
懒惰的线程安全单例实例对每个编码器来说都不容易理解
不,它实际上非常非常容易:
public class Singleton{ private final static Singleton instance = new Singleton(); private Singleton(){ ... } public static Singleton getInstance(){ return instance; } }
更好的是,让它成为一个枚举:
public enum Singleton{ INSTANCE; private Singleton(){ ... } }
它是线程安全的,它是懒惰的(初始化发生在类加载时,Java不会加载类,直到它们被首次引用)。
事实上,99%的时间你根本不需要延迟加载 。 剩下的1%,在0.9%以上,完全是懒惰的。
您是否运行过探查器并确定您的应用程序属于真正需要首先访问延迟加载的0.01%? 不这么认为。 那你为什么要浪费时间来编造这些Rube Goldbergesque代码可恶来解决一个不存在的问题呢?
对于比问题中提供的更具可读性(在我看来)的版本,可以参考Bill Pugh介绍的Initialization on Demand Holder惯用法 。 考虑到Java 5内存模型,它不仅是线程安全的,单例也是懒惰地初始化。
看起来过度工程对我来说。
我真的不知道帮助class有多帮助 。
首先,它使用的是双锁定语,并且它已被certificate一次又一次被破坏。
其次,如果你必须使用单例,为什么不初始化static final
实例。
public class Singleton1 { private static final Singleton1 instanceHolder = new Singletong1( ); public Singleton1 instance() { return instanceHolder; } private Singleton1() { System.out.println("Singleton1 instantiated"); } }
此代码是线程安全的,并且已经certificate可以正常工作。
查看Vineet Reynolds的答案,了解何时需要在第一次获取时初始化单例实例。 在许多情况下,我认为这种做法也是一种过度杀伤力。
由于Java内存模型和乱序执行的可能性,是不是双重检查锁定模式和在JIT编译器和多核/处理器系统上使用volatile 破坏 ?
更一般地说,似乎单身人士的框架对于本质上非常简单的正确实现模式来说是过度的。
我同意其他海报,并说这看起来有点矫枉过正,但是我说我确实认为这是初级开发人员可能出错的事情。 我认为,因为构造单例的供应商(如下所示)的行为几乎在所有情况下都是相同的,我很想将它作为默认行为放在LazyThreadSafeInstantiator
。 每次想要使用单例时使用自治内部类都非常麻烦。
@Override public Singleton1 get() { return new Singleton1(); }
这可以通过提供一个重载的构造函数来完成,该构造函数将Class转换为所需的单例。
public class LazyThreadSafeInstantiator implements Supplier { private final Supplier instanceSupplier; private Class toConstruct; private volatile T obj; public LazyThreadSafeInstantiator(Supplier instanceSupplier) { this.instanceSupplier = instanceSupplier; } public LazyThreadSafeInstantiator(Class toConstruct) { this.toConstruct = toConstruct; } @Override // http://en.wikipedia.org/wiki/Double-checked_locking public T get() { T result = obj; // Wikipedia: Note the usage of the local variable result which seems unnecessary. For some versions of the Java VM, it will make the code 25% faster and for others, it won't hurt. if (result == null) { synchronized(this) { result = obj; if (result == null) { if (instanceSupplier == null) { try { Constructor[] c = toConstruct.getDeclaredConstructors(); c[0].setAccessible(true); result = c[0].newInstance(new Object[] {}); } catch (Exception e) { //handle } result = } else { result = instanceSupplier.get(); } obj = result; } } } return result; } }
然后就可以这样使用。
private static final Supplier instanceHolder = new LazyThreadSafeInstantiator (Singleton1.getClass());
这是我的意见有点清洁。 您可以进一步扩展它以使用构造函数参数。
Lazy lazyX= new Lazy (){ protected X create(){ return new X(); }}; X x = lazyX.get();
abstract public class Lazy { abstract protected T create(); static class FinalRef { final S value; FinalRef(S value){ this.value =value; } } FinalRef ref = null; public T get() { FinalRef result = ref; if(result==null) { synchronized(this) { if(ref==null) ref = new FinalRef ( create() ); result = ref; } } return result.value; } }
除了线程中的第一个get(),所有get()调用都不需要同步或volatile读取。 实现了双重检查锁定的最初目标。