6月更文挑战|设计模式 —— 单例模式

550 阅读3分钟

这是我参与更文挑战的第2天,活动详情查看:更文挑战

单例模式

单例模式算是设计模式中最常被关注和使用的设计方案。

  • 特点:单例模式是保证系统全局只存在一个实例对象,其他对象都将通过它去执行相对的工作,从而降低重复创建实例的资源消耗。

实现方式

懒汉模式

单例模式中懒汉是最基础最简单的实现方式。当初始化类时候会实例化CPU对象。这样在一定程度上会增加资源开支,缺点就是未正式使用时就耗资源并且非线程安全。另外增加synchronized目的是为了在多线程环境下保证单例对象唯一性(非保证百分百唯一性),但同时因为需要同步多线程也带来了同步开销。因此懒汉模式并不是最优的单例模式。

public class CPU{
    private static CPU instance;
    private CPU(){}
    public static synchronized CPU getInstance(){
        if(instance == null){
            instance = new CPU();
        }
        return instance;
    }
}

Double CheckLock (DCL)

DCL是懒汉模式加强版,加强了对线程安全的保护,并且在初始化后就不再进行线程同步锁操作。DCL将同步锁synchronized放置在getInstance内部只有当instance为初始化时才进行同步锁判断,而当instance已经实例化不为空则不用进行线程同步。

public class CPU{
    private static int count = 0; // 增加一个计数防御反射调用
    private static CPU instance = null;
    private CPU(){}
    public static CPU getInstance(){
        if(instance == null){
            synchronized(CPU.class){
               instance = new CPU();
               if(count > 0){
                throw new RuntimeException("创建了两个实例");
               }
               count++;
        }
        return instance;
    }
}

但是在Java编译器中DCL还是可能存在时序问题,instance = new CPU()的过程并不是原子操作在多线程中执行还是会存在instance返回的是不完整对象的风险。 因此为了解决这个问题需要使用到volatile牺牲一点性能达到每次实例都能从主内存中读取保证唯一性。

再次优化后的DCL代码如下

public class CPU{
    private volatile static CPU instance = null;
    private CPU(){}
    public static CPU getInstance(){
        if(instance == null){
            synchronized(CPU.class){
               instance = new CPU();
            }
        }
        return instance;
    }
}

静态内部类

DCL方式事实上还不够完美,静态内部类模式能够保证不调用getInstance时不会初始化sInstance,只有在调用getInstance才会加载内部类SingletonHolder实例化sInstance。同时静态内部类又能保证线程安全,这是因为虚拟机对于类的client做了特殊处理,当多线程同时去初始化时确保只有一个线程会去执行,其他线程阻塞直至初始化结束。这就保证了静态内部类是线程安全,保证唯一性,延迟实例化对象。但静态内部类也不是十全十美,它不支持初始化入参。

public class CPU{
    private CPU(){}
    public static CPU getInstance(){
        return SingletonHolder.sInstance;
    }
    private static class SingletonHolder{
        private static final CPU sInstance = new CPU();
    }
}

枚举

枚举单例是最简单实现单例的方式,但也不是特别常用的模式。枚举特性JVM保证线程安全和唯一性。枚举另一个独一无二的特点就是防序列化,当被反序列化后枚举单例还是同一个实例对象并不会因为序列化后重新生成新的实例对象。

public enum CPU {

    INSTANCE;

    public void doSomething() {
        System.out.println("doSomething");
    }

}

总结

单例模式具有多种形态,核心原理就是将构造函数私有化,通过静态方法获取到唯一实例。这个过程需要保证实例唯一性,线程安全、防止序列表重新创建实例等问题。在实际业务场景中如何选择单例模式还是取决于开发者对于开发环境的判断:是否需要考虑高并发环境、资源消耗、语言环境版本等。