Java开发篇——设计模式(2)单例模式你真的了解吗(下)

核心提示那么怎么解决呢?为了防止这种指令重排的现象,java提供了volatile关键字用来保证指令执行的顺序,被volatile 修饰的变量那么在指令操作层也不会出现指令重排的现象。所以此时我们把代码稍微改正下就完美了,如下:上面的代码已经很好的

那么怎么解决呢?为了防止这种指令重排的现象,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注册模式经过了一下几个步骤:

先从注册表ConcurrentHashMap中获取单例对象,其中ConcurrentHashMap是一个线程安全的map集合

判断注册表中获取的实例对象是否为空,如果不为空则直接返回单例对象singletonObject

如果注册表中为空,则在同步锁中使用singletonFactory创建singletonObject并放入map集合中

关于单例模式,本文介绍了所有最经典的写法以及它背后的分析,一文在手,面试无忧。

 
友情链接
鄂ICP备19019357号-22