单例模式是比较简单的设计模式,但在工作中发现能正确规范书写的不多。下面列举一下Java中常见的实现方式。注意构造函数需要私有化。
1. 非懒加载模式。
public class Singleton {
private static Singleton instance = new Singleton();
private Singleton() {}
public static Singleton getInstance() {
return instance;
}
}
2. 懒加载模式。
A. 同步getInstance方法
public class Singleton {
private static Singleton instance;
private Singleton() {}
public synchronized static Singleton getInstance() {
if(instance == null) {
instance = new Singleton();
}
return instance;
}
}
B. 双重检查加锁
主要是避免每次getInstance都要加锁。也是实现最复杂的一个。注意instance必须设为volatile。因为通过两次对instance的判空来决定初始化,不使用volatile来保证线程读取的对象确实与内存中保存一致,会导致未初始化完成的对象被发布和被引用。
public class Singleton {
private static volatile Singleton instance;
private Singleton() {}
public static Singleton getInstance() {
if(instance == null) {
synchronized (Singleton.class) {
if(instance == null) {
instance = new Singleton();
}
}
}
return instance;
}
}
C. 延迟初始化占位类模式(我称为静态内部类)
通过静态内部类持有Instance,通过jvm分配静态内存时的同步机制,可以确保对象延迟初始化,并正确发布。
public class Singleton {
private Singleton() {}
private static final class Holder {
public static final Singleton instance = new Singleton();
}
public static Singleton getInstance() {
return Holder.instance;
}
}
D. 借用内联
通过声明一个内联元素,确保instance的唯一性。我认为完全没必要。为了实现单例,而改变对象的属性(class改为enum),代码可读性很差,只是借用下技巧而已。
public enum Singleton {
INSTANCE;
private Singleton() {}
public void work() {
//to do
}
}
综上所述,C是实现懒加载的最简单明了方式,项目中可以常用。不必考虑到反射来破坏单例,这个感觉已经走远了。还有甚者,在 延迟初始化占位类模式实现内部类的时候通过enum来替代,说确保enum构造函数只执行一次。我只能说,呵呵了。要做的话,用底层的UnSafe和Classloader什么不好做,可读性往往大于奇淫巧技。
双重检查加锁一般已经废弃,如果需要进一步了解,可以参考《Java Concurrency in practice》一书。