单例模式
Posted fightingtong
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了单例模式相关的知识,希望对你有一定的参考价值。
单例模式:
单例模式:(Singleton Pattern, SP)
确保一个类在任何情况下都绝对只有一个实例,并提供一个全局的访问点
创建型模式
应用场景:
公司CEO
部门经理
总结:
1、私有化构造器
2、保证线程安全
3、延迟加载
4、防止序列化和反序列化的破坏单例
5、防御反射攻击单例
源码中的体现:
ServletContext
ServletContextConfig
ApplicationContext
数据库的连接池
优点:
在内存中只有一个实例,减少内存的开销
缺点:
没有接口
============================================================================
饿汉式单例模式:
在类加载的时候就立即初始化,并且创建单例对象
绝对线程安全,在线程还没出现以前就实例化了,不可能存在访问安全问题
优点:
没有加任何锁,执行效率比较高,性能高
缺点:
类加载的时候就初始化,不管用与不用都占着空间,浪费了内存,有可能“占着茅坑不拉屎”
案例:
Spring中IoC容器 ApplicationContext 本身就是典型的饿汉式单例模式
饿汉式单例模式适用于单例对象较少的情况?
对象较少的情况指的是(单例数量已知并可控)
如果单例数量未知,例如spring中的单例,在写框架的时候是不知道用户会搞多少个单例出来的。
源码:
pattern.singleton.hungry.HungrySingleton
============================================================================
懒汉式单例模式:
被外部类调用的时候内部类才会加载。
基础的懒汉式写法,有一定的概率会出现两种不同的结果,这意味着懒汉式单例存在线程安全隐患
【如何优化代码,使得懒汉式单例模式在线程环境下安全?】
1、给getInstance()方法加上 【synchronized】 关键字,解决创建两个实例的问题,但是使得方法变成线程同步方法。
源码:pattern.singleton.lazy.LazySimpleSingletion
2、双重检测锁
优化性能问题,第一次检查是否要阻塞,第二次检查是否要重新创建实例
volatile 解决指令内存重排序
源码:pattern.singleton.lazy.LazyDoubleCheckSingleton
3、静态内部类(兼顾解决饿汉式的内存浪费问题和synchonized的性能问题,但是如果对构造函数不进行处理,存在被反射破环的风险)
优点:写法优雅,利用java语法特点,性能高
缺点:能够被反射破坏
源码:pattern.singleton.lazy.LazyStaticInnerClassSingleton
优点:节省了内存 线程安全
缺点:并发请求可能会创建两个实例
============================================================================
反射破化单例:
对于使用静态内部类方式实现的单例,如果适用反射来调用其构造方法,再调用getInstance()方法,则有两个不同的实例
源码:ReflectTest
============================================================================
序列化破化单例:
一个单例对象创建好后,有时候需要把对象序列化然后写入磁盘,下次使用时从磁盘读取并进行反序列化,转化未内存对象。但是反序列化的对象会被重新分配内存(即重新创建)。
如果序列化的对象时单例,就违背了单例模式的初衷,相当于破坏了单例。
如何保证序列化的情况下也能实现单例模式呢?
增加一个readResolve()方法
private Object readResolve(){ return INSTANCE;}
虽然增加readResolve()方法返回实例解决了单例模式被破坏的问题,但是实际上还是实例了两次(只不过新创建的对象没有被返回而已)
源码:pattern.singleton.seriable.SeriableSingleton
SeriableSingletonTest
反序列化:
将持久化的字节码内容,通过IO输入流读到内存中来
转化成一个java对象
============================================================================
注册式单例模式:
又叫登记式单例模式,就是将每一个实例都登记到某一个地方,使用唯一的标识获取实例。
注册式单例有两种:枚举式单例、容器式单例
枚举式单例:
不会被反射和序列化破化,是推荐的一种单例模式的实现方法
容器式单例:
适用于实例非常多的情况,便于管理。但是他是非线程安全的。
============================================================================
线程单例实现 ThreadLocal
ThreadLocal 不能保证其创建的对象全局唯一,但能保证在单个线程中是唯一的,天生是线程安全的
单例模式为了达到线程安全的目的,会给方法加上锁,以时间换空间
ThreadLocal 将所有的对象全部放在 ThreadLocalMap中,为每个线程都提供一个对象,以空间换时间来实现线程隔离的
============================================================================
单例模式总结:
单例模式可以保证内存中只有一个实例,减少内存的开销,避免对资源的多重占用。
高频的面试点
作业:
怎么解决容器式单例的线程安全?
以上是关于单例模式的主要内容,如果未能解决你的问题,请参考以下文章