搜索
写经验 领红包

单例模式编程(什么叫单例设计模式)

导语:您真的懂单例吗:程序员必须掌握的设计模式之单例模式

一、什么是单例模式

对象在系统中只存在一个实例,称为单例(Singleton)。反之,存在多个实例的,叫多例。虽然是设计模式中最简单的模式,但是很多同学却没有深入了解它,如果面试的时候问到这个问题,您能过关吗?(以下代码示例都采用Java语言)

二、为什么要使用单例模式

可以节省内存,保证对象数据的一致性。例如,配置类,系统中只要存在一个实例即可,多个实例既没必要也难维护。

三、如何实现单例模式

1、保证多线程单例

在多线程场景下,也要保证只有一个实例。常规方法是加同步锁。

2、保证特殊创建对象场景下的单例

常用的创建对象的方法有构造器和静态工厂,而特殊的创建对象方法还有反射、反序列化和克隆。我们必须保证特殊场景下也是单例的。

保证反射单例

由于通过反射机制,可以调用私有构造器,所以,存在多个实例的可能,保证反射下也能单例的方法是,在构造器中抛出异常:

图1 保证反射单例示例代码

保证反序列化单例

一个类如果实现了 java.io.Serializable接口,它就有可能被序列化和反序列化。在反序列化的时候,要保证单例,必须重写readResolve方法:

图2 保证反序列化单例示例代码

保证克隆单例

在克隆场景下,要保证单例,我们必须让对象不能实现cloneable接口。也就是让它无法克隆。

3、常规的6种实现方式(都支持线程安全)

饿汉式

该方式指类一加载就创建一个实例:

图3 饿汉式示例代码

懒汉式

延迟创建实例,需要时再加载,由于有同步操作,会有性能问题:

图4 懒汉式示例代码

双重校验锁

解决同步操作每次都要等待同步锁,导致性能降低的问题。使用volatile禁止指令重排,避免了指令重排导致的线程安全问题:

图5 双重校验锁示例代码

静态内部类

在静态内部类里面创建单例,由于静态内部类在初始化的时候会获得初始化锁,所以是线程安全的,不再需要同步操作,避免了性能问题:

图6 静态内部类示例代码

枚举

通过枚举来实现单例:

图7 枚举示例代码

注册登记式

每次初始化对象,都往固定的容器中去注册一下并进行缓存,下次使用的时候去缓存中读取:

图8 注册登记式示例代码

如果上述所示,通过getBean方法去获取类的实例。

四、实现方式对比

我们来对比一下上述6种实现方式,对比维度是:是否支持懒加载、是否支持反射单例、是否支持反序列化单例、是否支持克隆单例、是否没有性能问题。

图9 实现方式对比表

五、结论

在Spring环境下,我们推荐使用注册登记式,因为这本来就是Spring容器自带的实现方式。

非Spring环境下,推荐使用枚举方式。如果枚举不能用,比如单例模式类需要使用继承,推荐使用静态内部类或者双重校验锁。

除了枚举方式,其他方式都要注意反射、反序列化和克隆场景下的破坏单例问题。

本文内容由小茹整理编辑!