Java开发篇——设计模式单例模式你真的了解吗?(下)
Posted weixin_43802541
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Java开发篇——设计模式单例模式你真的了解吗?(下)相关的知识,希望对你有一定的参考价值。
那么怎么解决呢?为了防止这种指令重排的现象,java提供了volatile关键字用来保证指令执行的顺序,被volatile 修饰的变量那么在指令操作层也不会出现指令重排的现象。所以此时我们把代码稍微改正下就完美了,如下:
上面的代码已经很好的解决了线程安全和效率的问题,就是代码有点多,那么有没有更简单代码点的实现方式呢?
静态内部类
分析:静态内部类不会随着外部类的加载而加载 ,只有静态内部类的静态成员第一次被调用时才会被加载 ,即当getInstance()方法被调用时,SingleTonHoler才在SingleTon的运行时常量池里,把符号引用替换为直接引用,这时静态对象INSTANCE也真正被创建 。这种实现方式是巧妙地利用了JVM类加载机制的特性,保证了线程安全的问题。
之前提到单例的实现方式存在一些问题,在多线程高并发情况下,可以通过反射或者序列化的方式生成多个实例这样就破坏了单例,首先看序列化破坏:
输出结果:
输出的singleTon和singleTon2的hashCode不一样,那么证明它们并不是同一个对象,证明singleTon类生成了多个不一样的实例对象,序列化破坏了单例设计模式。怎么解决?其实很简单,我们只需要在单例类中添加一个方法即可:
加了之后的结果:
分析:添加了readResolve() 这个自定义方法之后,ObjectInputStream 在读取对象的时候就会直接调用序列化类中的readResolve()方法返回实例,不会再创建新的实例,这样就防止了序列化破坏单例设计。
反射破坏:
我们发现两个的hashCode不一样,那么就意味着生成了多个实例对象,有没有防止反射破坏的实现方式呢?
枚举类
反射尝试破坏运行结果:
分析:JVM中枚举的实现默认就是线程安全的,并且枚举类还提供了拒绝JVM通过反射创建实例对象,这样就防止了反射破坏单例模式。不足之处是枚举无法满足懒加载,并且枚举比静态实例变量要占用更多的内存。
上面提到了很多种高并发下线程安全的单例模式实现,能回答了这么多种方式并且可以对各种实现方式进行分析点评,其实已经深得面试官的喜爱了。
但是有的面试官又要问:“那么你项目开发中或者java流行框架spring中使用了那种方式的单例实现呢?”
Spring框架是java面试中老生常谈的问题,在spring中,bean的创建默认就是单例模式的设计;那spring是饿汉式单例还是懒汉式单例呢?如果你不了解它,顺着面试官的提问回答是饿汉还是懒汉,那基本上这个问题上就
Spring框架对单例的支持是采用注册(登记)模式进行实现的,我们直接来看spring登记模式的实现,新版的spring的注册模式的代码,从类AbstractBeanFactory的getBean(…) 方法中来看spring源码,我们可以发现具体的单例实现在类DefaultSingletonBeanRegistry的getSingleton()方法中:
分析:spring的源码中可以看出,spring注册模式经过了一下几个步骤:
(1)先从注册表ConcurrentHashMap中获取单例对象,其中ConcurrentHashMap是一个线程安全的map集合
(2)判断注册表中获取的实例对象是否为空,如果不为空则直接返回单例对象singletonObject
(3)如果注册表中为空,则在同步锁中使用singletonFactory创建singletonObject并放入map集合中
关于单例模式,本文介绍了所有最经典的写法以及它背后的分析,一文在手,面试无忧。
以上是关于Java开发篇——设计模式单例模式你真的了解吗?(下)的主要内容,如果未能解决你的问题,请参考以下文章