Java五种单例模式写法介绍和总结

为什么使用

确保某个类在程序中只有一个实例

实现方法

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调用实例对象

总结

  1. 推荐使用类级内部类或枚举实现单例。
  2. 若要求实现序列化,则采用枚举单例。

参考

《JAVA与模式》之单例模式
全面理解Java内存模型(JMM)及volatile关键字

猜你喜欢

转载自blog.csdn.net/Moon_Cai/article/details/80144104
今日推荐