为什么使用
确保某个类在程序中只有一个实例
实现方法
1. 饿汉式
以空间换时间,实例在类加载时就被创建
public class EagerSingleton{
private static EagerSingleton instance = new EagerSingleton();
private EagerSingleton(){}
public static EagerSingleton getInstance(){
return instance;
}
}
注:静态字段初始化由JVM保证线程安全
2. 懒汉式
以时间换空间,实例在类被使用时才创建
public class LazySingleton{
private static LazySingleton instance = null;
private LazySingleton(){}
private static synchronized LazySingleton getInstance(){
if(instance == null){
instance = new LazySingleton();
}
return instance;
}
}
懒汉式的实现是线程安全,降低了访问的速度。
3. 双重检查加锁
懒汉式单例的进一步优化,使得线程只在对象实例为空时进入同步块
public class DoubleCheckSingleton{
private volatile static DoubleCheckSingleton instance = null;
private DoubleCheckSingleton(){}
private static DoubleCheckSingleton getInstance(){
//查询实例不存在才进入同步代码块中
if(instance == null){
synchronized(DoubleCheckSingleton.class){
// 在进行一次检查,实例不存在才创建
if(instance == null){
instance = new DoubleCheckSingleton();
}
}
}
return instance;
}
}
思考:为什么使用volatile?
因为instance = new DoubleCheckSingleton();这个操作可以分为一下三步走:
memory = allocate(); //1.分配对象内存空间
instance(memory); //2.初始化对象
instance = memory; //3.设置instance指向刚分配的内存地址,此时instance!=null
因步骤2和3不存在数据依赖,故可能会发生指令重排
memory = allocate(); //1.分配对象内存空间
instance = memory; //3.设置instance指向刚分配的内存地址,此时instance!=null,但是对象还没有初始化完成!
instance(memory); //2.初始化对象
进而,导致个别线程获取到没有初始化的instance对象(但此时它又不会等于null),出现错误。
4. lazy Initialization Holder Class(推荐)
通过类级内部类实现单例模式,既能延迟加载,又能保证线程安全
注:类级内部类,即静态成员式内部类
public class LazyHolderSingleton{
private LazyHolderSingleton(){}
/**
* 因内部类实例与外部类实例没有绑定关系
* 故只在被调用时才会被装载,以此实现延迟加载
*/
private static class SingletonHolder{
private static LazyHolderSingleton instance = new LazyHolderSingleton();
}
private static LazyHolderSingleton getInstance(){
return SingletonHolder.instance;
}
}
5. 枚举单例(推荐)
写法最简单,有序列化和线程安全的保证
注:传统单例一旦实现序列化,将不再保持单例,但枚举单例不会。
public enum EnumSingleton{
instance;
}
通过EnumSingleton.instance调用实例对象
总结
- 推荐使用类级内部类或枚举实现单例。
- 若要求实现序列化,则采用枚举单例。