单例总结
0)Eager initialization
如果程序一开始就需要某个单例,并且创建这个单例并不那么费时
public class Singleton {
private static final Singleton INSTANCE = new Singleton();
private Singleton() {}
public static Singleton getInstance() {
return INSTANCE;
}
}
这种实现方式有几个特点:
- 实例一开始就被创建(Eager initialization)。
- getInstance()方法不需要加synchronize来解决多线程同步的问题
- final关键字确保了实例不可变,并且只会存在一个。
1)Lazy initialization
Volatile(不稳定的),这就指示JVM,这个变量是不稳定的,每次使用它都到主存中进行读取。一般说来,多任务环境下各任务间共享的标志都应该加volatile修饰。
Volatile修饰的成员变量在每次被线程访问时,都强迫从共享内存中重读该成员变量的值。而且,当成员变量发生变化时,强迫线程将变化值回写到共享内存。这样在任何时刻,两个不同的线程总是看到某个成员变量的同一个值。
懒加载的方式使得单例会在第一次使用到时才会被创建.先看一种有隐患的写法:
public final class LazySingleton {
private static volatile LazySingleton instance = null;
// private constructor
private LazySingleton() {
}
public static LazySingleton getInstance() {
if (instance == null) {
synchronized (LazySingleton.class) {
instance = new LazySingleton();
}
}
return instance;
}
}
请注意:上面的写法其实非线程安全的,假设两个Thread同时进入了getInstance方法,都判断到instance==null,然后会因为synchronized的原因,逐个执行,这样就得到了2个实例。解决这个问题,需要用到典型的double-check方式,如下:
public class LazySingleton {
private static volatile LazySingleton instance = null;
private LazySingleton() {
}
public static LazySingleton getInstance() {
if (instance == null) {
synchronized (LazySingleton .class) {
if (instance == null) {
instance = new LazySingleton ();
}
}
}
return instance;
}
}
另外一个更简略直观的替代写法是:
public class LazySingleton {
private static volatile LazySingleton instance = null;
private LazySingleton() {
}
public static synchronized LazySingleton getInstance() {
if (instance == null) {
instance = new LazySingleton ();
}
return instance;
}
}
2)Static block initialization
如果我们对程序的加载顺序有点了解的话,会知道Static block的初始化是执行在加载类之后,Constructor被执行之前。
public class StaticBlockSingleton {
private static final StaticBlockSingleton INSTANCE;
static {
try {
INSTANCE = new StaticBlockSingleton();
} catch (Exception e) {
throw new RuntimeException("Error, You Know This, Haha!", e);
}
}
public static StaticBlockSingleton getInstance() {
return INSTANCE;
}
private StaticBlockSingleton() {
// ...
}
}
上面的写法有一个弊端,如果我们类有若干个static的变量,程序的初始化却只需要其中的1,2个的话,我们会做多余的static initialization。
3)Bill Pugh solution
University of Maryland Computer Science researcher Bill Pugh有写过一篇文章initialization on demand holder idiom。
public class Singleton {
// Private constructor prevents instantiation from other classes
private Singleton() { }
/**
* SingletonHolder is loaded on the first execution of Singleton.getInstance()
* or the first access to SingletonHolder.INSTANCE, not before.
*/
private static class SingletonHolder {
public static final Singleton INSTANCE = new Singleton();
}
public static Singleton getInstance() {
return SingletonHolder.INSTANCE;
}
}
SingletonHolder类会在你需要的时候才会被初始化,而且它不影响Singleton类的其他static成员变量的使用。这个方法是线程安全的并且避免了使用volatile与synchronized。
4)Using Enum
这是最简便安全的方法。没有明显的缺点,并且避免了下面要讲到的序列化的隐患。
public enum Singleton {
INSTANCE;
public void execute (String arg) {
// perform operation here
}
}
Serialize and de-serialize
在某些情况下,需要实现序列化的时候,普通的单例模式需要添加readResolve的方法,不然会出现异常。
public class DemoSingleton implements Serializable {
private volatile static DemoSingleton instance = null;
public static DemoSingleton getInstance() {
if (instance == null) {
instance = new DemoSingleton();
}
return instance;
}
protected Object readResolve() {
return instance;
}
private int i = 10;
public int getI() {
return i;
}
public void setI(int i) {
this.i = i;
}
}
仅仅有上面的还不够,我们需要添加serialVersionUID,例子详见下面的总结。
Conclusion
实现一个功能完善,性能更佳,不存在序列化等问题的单例,建议使用下面两个方式之一:
Bill Pugh(Inner Holder)
public class DemoSingleton implements Serializable {
private static final long serialVersionUID = 1L;
private DemoSingleton() {
// private constructor
}
private static class DemoSingletonHolder {
public static final DemoSingleton INSTANCE = new DemoSingleton();
}
public static DemoSingleton getInstance() {
return DemoSingletonHolder.INSTANCE;
}
protected Object readResolve() {
return getInstance();
}
}
Enum
public enum Singleton {
INSTANCE;
public void execute (String arg) {
// perform operation here
}
}