本文基于Java语言实现
单实例Singleton设计模式可能是被讨论和使用的最广泛的一个设计模式了,这可能也是面试中问得最多的一个设计模式了。这个设计模式主要目的是想在整个系统中只能出现一个类的实例。
下面是一些常见的写法和存在的问题,以及如何优雅的来实现一个单例。
1.懒汉模式(线程不安全)
public class Singleton {
private static Singleton sInstance;
private Singleton(){};
public static Singleton getInstance() {
if (sInstance == null) {
sInstance = new Singleton();
}
return sInstance;
}
}
该方式存在的问题是:
在多线程情况下,如果多个线程同时调用getInstance()
的话,那么,可能会有多个进程同时通过 (sInstance== null)
的条件检查,于是,多个实例就创建出来,并且很可能造成内存泄露问题。
为了解决多线程安全问题,我们考虑给方法加锁,于是有了下面这种形式:
2.懒汉模式(线程安全)
public class Singleton1 {
private static Singleton1 sInstance;
private Singleton1() {
};
public static synchronized Singleton1 getInstance() {
if (sInstance == null) {
sInstance = new Singleton1();
}
return sInstance;
}
}
//或者
public class Singleton2 {
private static Singleton2 sInstance;
private Singleton2() {
};
public static Singleton2 getInstance() {
synchronized(Singleton2.class){
if (sInstance == null) {
sInstance = new Singleton2();
}
}
return sInstance;
}
}
呐,这种形式的问题主要是效率低下,为什么呢?
因为只要是进入getInstance()的线程都得被同步,创建对象的动作只有一次,后面的动作全是读取那个成员变量,这些读取的动作是不需要线程同步的。为了一个初始化的创建动作,居然让我们搭上了所有的读操作,严重的影响了后续的性能!
3.饿汉模式
public class Singleton {
private static Singleton sInstance = new Singleton();
private Singleton() {
};
public static Singleton getInstance() {
return sInstance;
}
}
饿汉模式在类加载的时候就对实例进行创建,实例在整个程序周期 都存在。它的好处是只在类加载的时候创建一次实例,不会存在多个线程创建多个实例的情况,避免了多线程同步的问题。它的缺点也很明显,即使这个单例没有用到也会被创建,而且在类加载之后就被创建,内存就被浪费了。
4.双重校验锁
public class Singleton {
private volatile static Singleton sInstance;
private Singleton() {
};
public static Singleton getInstance() {
if (sInstance == null) {
synchronized (Singleton.class) {
if (sInstance == null) {
sInstance = new Singleton();
}
}
}
return sInstance;
}
}
这种写法应该说可以基本可以解决上面提到的这些问题,但是这个仅在Java 1.5版后有用,1.5版之前用这个变量也有问题,因为老版本的Java的内存模型是有缺陷的。当然现在也很少有用1.5之前的版本了。
不过,这种写法稍显复杂,一点都不优雅!
5.静态内部类
public class Singleton {
private Singleton() {
};
public static Singleton getInstance() {
return Holder.INSTANCE;
}
private static class Holder {
private static final Singleton INSTANCE = new Singleton();
}
}
这是《Effective Java》中推荐的写法,由于 Holder
是私有的,除了 getInstance()
之外没有办法访问它,因此它只有在getInstance()
被调用时才会真正创建;同时读取实例的时候不会进行同步,没有性能缺陷;也不依赖 JDK 版本。
6.枚举类型
public enum Singleton {
INSTANCE;
public void whatererMethod() {
}
}
这是新版的《Effective Java》推荐的写法。
通过反编译可知枚举实际上就是一个继承Enum的类。所以它的本质还是一个类,因为枚举的特点,它只会有一个实例,同时保证了线程安全、反射安全和反序列化安全。