码迷,mamicode.com
首页 > 其他好文 > 详细

单例模式

时间:2020-03-14 16:54:46      阅读:58      评论:0      收藏:0      [点我收藏+]

标签:rac   ble   载器   bsp   多次   别人   两种   测试   span   

一、单例模式

1、单例模式(Singleton Pattern):指确保一个类在任何情况下都绝对只有一个实例,私有化其所有构造方法,并提供一个全局访问点。(属于创建型模式)

2、适用场景

确保任何情况下都绝对只有一个实例(如ServletContext、ServletConfig、ApplicationContext、DBPool)

3、常见写法

  • 饿汉式单例
  • 懒汉式单例
  • 注册式单例
  • ThreadLocal单例

4、优点

  • 在内存中只有一个实例,减少了内存开销
  • 可以避免对资源的多重占用
  • 设置全局访问点,严格控制访问

5、缺点

  • 没有接口,扩展困难
  • 如果要扩展单例对象,只有修改代码,没有其他途径

二、饿汉式单例

1、饿汉式单例:指在单例类首次加载时就创建实例。

2、缺点:如果这个对象实例从头到尾都没有被使用过的话,会浪费内存空间。

例子:下面两种方式都可以。 

① 常规:

技术图片

② 静态代码块:

技术图片

提问:这两种饿汉式为什么成员变量要加final关键字?

防止该实例被篡改。如果不加final的话,该实例有可能被别人给覆盖了。

三、懒汉式单例

懒汉式单例:被外部类调用时才创建实例。

例子:下面三种方式都可以。(线程安全)

① 粗粒度加锁:

技术图片

② 细粒度加锁:(双重校验锁)

技术图片

提问:这两种懒汉式为什么成员变量不加final关键字?

如果加final的话,singleton对象引用就不能被赋值了。

静态内部类

技术图片

四、反射破坏单例

提问:如下图代码,虽然以上懒汉式和饿汉式的构造方法都私有了,但还是没办法防止反射攻击,如何解决?

反射攻击例子:

技术图片

解决:

技术图片

运行结果:

技术图片

五、序列化破坏单例

当我们将一个单例对象创建好,有时候需要将对象序列化然后写入到磁盘,下次使用时再从磁盘中读取到对象,反序列化转化为内存对象。反序列化后的对象会重新分配内存,即重新创建。那如果序列化的目标对象为单例对象,就违背了单例模式的初衷,相当于破坏了单例,如下代码:

技术图片

编写测试代码:

技术图片

运行结果:

技术图片

从运行结果中可以看出,反序列化后的对象和手动创建的对象是不一致的,实例化了两次,违背了单例的设计初衷。那么,我们如何保证序列化的情况下也能够实现单例?其实很简单,只需要增加readResolve()方法即可,如下代码:

技术图片

再看运行结果:(原理这里就不解释了,实际上还是实例化了两次,只不过新创建的对象没有被返回)

技术图片

六、注册式单例

注册式单例:将每一个实例都登记缓存到某一个地方中,使用唯一标识获取实例。

注册式单例有两种:容器缓存枚举登记

① 先看枚举式单例的例子,创建一个EnumSingleton枚举类:(能防止序列化和反射攻击破坏单例)

技术图片

测试代码:

技术图片

运行结果:说明能防止序列化破坏单例。(枚举类型其实通过类名和Class对象找到一个唯一的枚举对象。因此,枚举对象不可能被类加载器加载多次)

技术图片

再测试代码:

技术图片

运行结果:说明能防止反射攻击破坏单例。

技术图片

再测试代码:

技术图片

运行结果:这时错误更明显了,从JDK层面就明确规定不能用反射来创建枚举类型

技术图片

对EnumSingleton.class进行反编译,发现如下代码:

枚举类单例在静态代码块中就给INSTANCE进行了赋值,是饿汉式单例的体现。

技术图片

因此,《Effective Java》中推荐这种写法,从JDK层面就为枚举不被序列化和反射破坏来保驾护航。

② 再看容器缓存单例的写法,创建一个ContainerSingleton类:

技术图片

容器式写法适用于创建实例非常多的情况,便于管理。这里加上synchronized锁就是线程安全的。Spring中也用,如AbstractAutowireCapableBeanFactory。

七、ThreadLocal线程单例

ThreadLocal不能保证其创建的对象是全局唯一,但能保证在单个线程中是唯一的,且天生线程安全。如下代码:

技术图片

写一下测试代码:

技术图片

运行结果:(单个线程内才会是同个对象)

技术图片

上面的单例模式为了达到线程安全的目的,给方法上锁,以时间换空间。而ThreadLocal将所有对象全部放在ThreadLocalMap中,为每个线程都提供一个对象,实际上是以空间换时间来实现线程间隔离的。

八、知识重点总结

1、私有化构造器

2、保证线程安全

3、延迟加载

4、防止序列化和反序列化破坏单例

5、防御反射攻击单例

单例模式

标签:rac   ble   载器   bsp   多次   别人   两种   测试   span   

原文地址:https://www.cnblogs.com/ZekiChen/p/12448403.html

(0)
(0)
   
举报
评论 一句话评论(0
登录后才能评论!
© 2014 mamicode.com 版权所有  联系我们:gaon5@hotmail.com
迷上了代码!