单例模式属于创建型模式。
意图:保证一个类仅有一个实例,并提供一个访问它的全局访问点。
主要解决:一个全局使用的类频繁地创建与销毁。何时使用:当您想控制实例数目,节省系统资源的时候。
特点: ① 单例类只能有一个实例。
② 单例类必须自己自己创建自己的唯一实例。
③ 单例类必须给所有其他对象提供这一实例。
要点:① 私有的构造方法。② 指向自己实例的私有静态引用。③ 以自己实例为返回值的静态的公有的方法。
优点:①在内存里只有一个实例,减少了内存的开销,尤其是频繁的创建和销毁实例(比如管理学院首页页面缓存)。
②避免对资源的多重占用(比如写文件操作)。 ③可以全局访问。
缺点:没有接口,不能继承,与单一职责原则冲突,一个类应该只关心内部逻辑,而不关心外面怎么样来实例化。
UML图:
单例模式有很多种写法,具体代码如下:
1. 懒汉式(最简单版本):
public class Singleton {
private static Singleton instance;
private Singleton() {}
public static Singleton getInstance() {
if (instance == null) {
instance = new Singleton();
}
return instance;
}
}
这种实现最简单,但是非线程安全。
2. 懒汉式(synchronized版本):
public class Singleton {
private static Singleton instance;
private Singleton (){}
public static synchronized Singleton getInstance() {
if (instance == null) {
instance = new Singleton();
}
return instance;
}
}
比第一种实现好在线程安全。但这种方法也有问题,就是会强制除进入方法的线程外的其他线程等待,会降低程序的执行效率。
3. 懒汉式(双重检查版本):
public class Singleton {
private static Singleton instance;
private Singleton() {}
public static Singleton getInstance() {
if (instance == null) {
synchronized (Singleton.class) {
if (instance == null) {
instance = new Singleton();
}
}
}
return instance;
}
}
这种方法双重检查(Double-Check)。第一个if (instance==null)其实是为了解决第2种方法的效率问题,只有instance为null的时候才进入synchronized的代码段;第二个if (instance==null)则是跟第2种方法一样是为了防止可能出现多个实例的情况。
这种方法还是会出现问题,主要是由于instance=new Singleton()这句不是一个原子操作,JVM指令重排会导致问题。
事实上在 JVM 中这句话大概做了下面 3 件事情。
① 给 singleton 分配内存
② 调用 Singleton 的构造函数来初始化成员变量,形成实例
③ 将singleton对象指向分配的内存空间(执行完这步 singleton才是非 null 了)
但是在 JVM 的即时编译器中存在指令重排序的优化。也就是说上面的第二步和第三步的顺序是不能保证的,最终的执行顺序可能是 1-2-3 也可能是 1-3-2。如果是后者,则在 3 执行完毕、2 未执行之前,被线程二抢占了,这时 instance 已经是非 null 了(但却没有初始化),所以线程二会直接返回 instance,然后使用,然后顺理成章地报错。总结来说就是线程T1对instance的写操作没有完成,线程T2就执行了读操作。
4. 懒汉式(终极版本):
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;
}
}
这种方法跟第3种方法的区别就在于将instance声明为volatile,保证不会出现指令重排,这样在按顺序执行完1-2-3之前不会出现另外一个线程调用读操作。
5. 饿汉式(最简单版本):
public class Singleton {
private static Singleton instance = new Singleton();
private Singleton(){};
public static Singleton getInstance() {
return instance;
}
}
最简单,线程安全;但可能由于初始化得太早造成资源的浪费。
6. 静态内部类实现
public class Singleton {
private static class SingletonHolder {
private static final Singleton INSTANCE = new Singleton();
}
private Singleton () {}
public static final Singleton getInstance() {
return SingletonHolder.INSTANCE;
}
}
7. 枚举实现
public class SingletonExample {
// 私有构造函数
private SingletonExample() {
}
public static SingletonExample getInstance() {
return Singleton.INSTANCE.getInstance();
}
private enum Singleton {
INSTANCE;
private SingletonExample singleton;
// JVM保证这个方法绝对只调用一次
Singleton() {
singleton = new SingletonExample();
}
public SingletonExample getInstance() {
return singleton;
}
}
}