单例模式

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中,为每个线程都提供一个对象,以空间换时间来实现线程隔离的


============================================================================

单例模式总结:

  单例模式可以保证内存中只有一个实例,减少内存的开销,避免对资源的多重占用。

  高频的面试点

 

作业:
  怎么解决容器式单例的线程安全?



以上是关于单例模式的主要内容,如果未能解决你的问题,请参考以下文章

单例模式(单例设计模式)详解

Java模式设计之单例模式(二)

单例模式(饿汉式单例模式与懒汉式单例模式)

单例模式

单例模式

设计模式之单例模式