一、定义
单例(Singleton)模式的定义:指一个类只有一个实例,且该类能自行创建这个实例的一种模式。
二、实现原理
使用一个私有构造函数、一个私有静态变量以及一个公有静态函数来实现。
私有构造函数保证了不能通过构造函数来创建对象实例,只能通过公有静态函数返回唯一的私有静态变量。
三、实现方式
1、饿汉式
2、懒汉式
3、静态内部类实现
4、枚举实现
四、饿汉式
直接实例化对象,优点:线程安全,能简单实现单例。缺点:JVM在加载这个类时马上创建唯一单例实例,未使用则会浪费资源。同时加大创建运行负担。
public class Singleton {
private static Singleton singleton = new Singleton();
private Singleton() {}
public static Singleton getInstance() {
return singleton;
}
}
五、懒汉式(双重加锁)
public class Singleton {
private volatile static Singleton singleton;//volatile关键字禁止JVM指令重排
private Singleton() {}
public static Singleton getSingleton() {
if (singleton == null) {//第一次校验,避免实例化对象后仍然加锁
synchronized (Singleton.class) {
if (singleton == null) {//第二次校验,避免多个线程实例化对象
singleton = new Singleton();
}
}
}
return singleton;
}
}
1、双重加锁的意义
加锁是为了防止多个线程同时通过为空判断,造成多次创建对象。第一次校验为了避免实例化对象以后,每一次都会走加锁操作。第二次校验是防止多个线程都会进入第一个 if 语句块内。虽然在 if 语句块内有加锁操作,但是两个线程都会执行 singleton =new Singleton(); 语句,只是按顺序执行,就会造成多次实例化。
2、volatile 关键字的意义
volatile 关键字主要用于禁止JVM指令重排。
singleton =new Singleton(); 创建对象会经过三个步骤:
(1)分配内存空间
(2)初始化对象
(3)将对象指向分配的内存地址
但是JVM具有指令重排特性,有可能先执行3再执行2,在多线程的情况下有可能出现thread1执行力3而没有执行2,同时thread2也进入了,判断singleton不为空,就将其返回,但是事实上对象还未被成功初始化。使用volatile关键字禁止JVM指令重排解决该问题。
六、静态内部类实现
public class Singleton {
private Singleton() {}
private static class SingletonHolder {
private static final Singleton INSTANCE = new Singleton();
}
public static Singleton getInstance() {
return SingletonHolder.INSTANCE;
}
}
当 Singleton 类被加载时,静态内部类 SingletonHolder 没有被加载进内存。只有当调用 getInstance()方法从而触发 SingletonHolder.INSTANCE 时 SingletonHolder 才会被加载,此时初始化 INSTANCE 实例,并且 JVM 能确保 INSTANCE 只被实例化一次。
这种方式不仅具有延迟初始化的好处,而且由 JVM 提供了对线程安全的支持。
七、枚举实现
public enum Singleton {
INSTANCE;
private String name;
public void doSomething() {
//...
}
}
默认枚举实例的创建是线程安全的,并且在任何情况下都是单例,上述讲的几种单例模式实现中,有一种情况下他们会重新创建对象,那就是反序列化,将一个单例实例对象写到磁盘再读回来,从而获得了一个实例。反序列化操作提供了readResolve方法,这个方法可以让开发人员控制对象的反序列化。在上述的几个方法示例中如果要杜绝单例对象被反序列化是重新生成对象,就必须加入如下方法:
private Object readResolve()throws ObjectStreamException {
return singleton;
}
枚举单例的优点就是简单,但是大部分应用开发很少用枚举,可读性并不是很高。