单例模式的应用场景
单例模式(Singleton Pattern)是指确保一个类在任何情况下都绝对只有一个实例,并 提供一个全局访问点。单例模式是创建型模式。在 J2EE 标准中,ServletContext、ServletContextConfig 等;在 Spring 框架应用中 ApplicationContext;数据库的连接池也都是单例形式。
饿汉式单例
饿汉式单例是在类加载的时候就立即初始化,并且创建单例对象。绝对线程安全,在线 程还没出现以前就是实例化了,不可能存在访问安全问题。
优点:没有加任何的锁、执行效率比较高,在用户体验上来说,比懒汉式更好。
缺点:类加载的时候就初始化,不管用与不用都占着空间,可能会浪费内存。
1 |
|
懒汉式单例
懒汉式单例的特点是:被外部类调用的时候内部类才会加载。
double-check
1 |
|
静态内部类
1 |
|
这种形式兼顾饿汉式的内存浪费,也兼顾synchronized
性能问题。内部类一定是要在方 法调用之前初始化,巧妙地避免了线程安全问题。
反射破坏单例
上面介绍的单例模式的构造方法除了加上private
以外,没有做任何处理。如果我们使用反射来调用其构造方法,然后,再调用getInstance()
方法,应该就会两个不同的实例。
1 | Class<InnerClassSingleton> clzz = InnerClassSingleton.class; |
解决方案:我们在其构造方法中做一些限制,一旦出现多次重复创建,则直接抛出异常。来看优化后的代码:
1 |
|
序列化破坏单例
当我们将一个单例对象创建好,有时候需要将对象序列化然后写入到磁盘,下次使用时再从磁盘中读取到对象,反序列化转化为内存对象。反序列化后的对象会重新分配内存,即重新创建。那如果序列化的目标的对象为单例对象,就违背了单例模式的初衷,相当于破坏了单例。
解决方案:只需要给单例对象增加 readResolve()方法即可。
1 | private Object readResolve(){ |
源码分析:ObjectInputStream.readObject() -> readObject0(false)
:
重点就是readOrdinaryObject(unshared)方法:
发现调用了ObjectStreamClass
的isInstantiable()
方法,而isInstantiable()
里面的代码如下:
代码非常简单,就是判断一下构造方法是否为空,构造方法不为空就返回 true。意味着,只要有无参构造方法就会实例化。
接着看readOrdinaryObject(unshared)
方法:
如果要反序列化的对象存在readResolve()方法,就调用该方法并将方法的返回结果作为readObject的返回结果。
因此,我们新增readResolve()方法,并返回当前单例实例即可防止反序列化破坏单例。
但是,我们通过分析源码以及调试,我们可以看到实际上实例化了两次,只不过新创建的对象没有被返回而已。那如果,创建对象的动作发生频率增大,就意味着内存分配开销也就随之增大。
注册式单例
注册式单例又称为登记式单例,就是将每一个实例都登记到某一个地方,使用唯一的标 识获取实例。注册式单例有两种写法:一种为容器缓存,一种为枚举登记。
枚举式单例
1 | public enum EnumSingleton { |
使用jad反编译EnumSingleton.class文件,得到如下信息
- 枚举EnumSingleton实际上被编译成了一个继承Enum的EnumSingleton类
- 类的构造方法被私有了
- 在静态代码块中,EnumSingleton就被实例化了。
综上,枚举式单例实际上也是一种饿汉式单例的实现。自然保证了线程安全相关问题。
枚举式单例避免反序列化破坏单例?
源码分析:
ObjectInputStream.readObject() -> readObject0(false)
:
我们看到在readObject0()
中调用了readEnum()
方法,来看readEnum()
中代码实现:
我们发现枚举类型其实通过枚举名和 Class 对象类找到一个唯一的枚举对象。因此,枚举对象不可能被类加载器加载多次。
枚举式单例避免反射破坏单例?
Constructor.newInstance(..)
源码:
在newInstance()方法中做了强制性的判断,如果修饰符是 Modifier.ENUM 枚举类型,直接抛出异常。
综上:枚举式单例从JDK层面保证了反序列化和反射时的安全性。枚举式单例也是《Effective Java》书中推荐的一种单例实现写法。
容器缓存单例
1 | public class ContainerSingleton { |
容器式写法适用于创建多种类型单例的情况,便于统一管理,例如spring中的ioc容器。
源码:https://github.com/chentianming11/design-pattern
singleton包!