为什么要用单例模式 (单例模式的意义)
确保类只有一个实例
单例模式的构造函数为什么是private
防止外部实例化、防止继承和子类化
单例模式是怎么保存唯一实例的?
内部设置一个静态成员变量来保证唯一的实例
饿汉式
优点:线程安全,没有加锁,执行效率会提高。
缺点:类加载时就初始化,浪费内存。
public class Singleton {
private static Singleton instance = new Singleton();
private Singleton (){}
public static Singleton getInstance() {
return instance;
}
}
懒汉式
线程不安全的实现方式
public class Singleton {
private static Singleton instance;
private Singleton (){}
public static Singleton getInstance() {
if (instance == null) {
instance = new Singleton();
}
return instance;
}
}
在多线程下可能会产生多个实例。不可用。
线程安全的实现方式
优点:第一次调用才初始化,避免内存浪费。
缺点:必须加锁 synchronized 才能保证单例,但加锁会影响效率。
public class Singleton {
private static Singleton instance;
private Singleton (){}
public static synchronized Singleton getInstance() {
if (instance == null) {
instance = new Singleton();
}
return instance;
}
}
引入了synchronized关键字加锁,会引入性能开销。
双重校验锁DCL 线程安全
public class Singleton {
private volatile static Singleton singleton;
private Singleton (){}
public static Singleton getSingleton() {
if (singleton == null) {
synchronized (Singleton.class) {
if (singleton == null) {
singleton = new Singleton();
}
}
}
return singleton;
}
}
为什么要使用volatile关键字?
禁止指令重排序。
singleton = new Singleton();
在对象创建的过程中,不是只有一条指令,而是三条指令。
第一条指令是在内存中开辟空间,然后对对象进行半初始化。
第二条指令是对对象进行初始化。
第三条指令是对象指向内存地址。
问题的出现是因为在创建对象的时候,第二条指令和第三条指令会进行重排序。假设此时线程A通过第二次检查,在创建对象的过程中,第二条指令和第三条指令出现重排序,此时instance指向的是一个半初始化的对象。如果在这个时候线程B进来了,在执行第一次检查的时候发现instance并非为空,这时就直接返回了半初始化的instance,从而会出现问题。
使用volatile时为了确保在多线程环境下,所有线程都看到一个一致的状态。
静态内部类
使用静态内部类来实现懒汉式单例模式,保证线程安全和性能。
这种方式能达到双检锁方式一样的功效,但实现更简单。
public class Singleton {
private Singleton() {}
private static class SingletonHolder {
private static final Singleton INSTANCE = new Singleton();
}
public static Singleton getInstance() {
return SingletonHolder.INSTANCE;
}
}
枚举 jdk1.5起 线程安全
不能使用反射来获取构造函数进行构造
无法满足懒加载
public enum PersonSingleton {
INSTANCE;
}