常见设计模式-单例模式(1)

定义:确认任何情况下都绝对只有一个实例

优点:

       在内存中只有一个实例减少了内存开销。

       可以避免对多资源的多重占用

      设置全局访问点,严格控制访问。

缺点:

       没有接口,扩展困难。

       如果要扩展单例对象,只有修改代码,没有其他途径。

       不符合开闭原则。

步骤:

      私有化构造器

       保证线程安全

       延迟加载

       防止序列化和反序列化

       防御反射攻击单例

例如:ServletContext、ServletConfig、ApplicationContext、DBpool等类

常见写法:  1、饿汉式单例

                     2、懒汉式单例

                     3、注册式单例

饿汉式单例:

定义:在单例类首次加载时就创建单例。不管用不用,先吃饱在说。

缺点:浪费内存空间。

 

懒汉式单例

定义:被外部类调用时才创建单例

因为线程的随机性,会抢占CPU,所以会引发线程安全问题。

情况1:

常见设计模式-单例模式(1)

当线程1实例化完,产生对象  @534后,然后线程0才进入该方法中

常见设计模式-单例模式(1)

那么此时,lazy已经实例化完毕,对象是唯一的,没有线程安全问题。

情况2:

当线程1 和线程0 同时处于该方法中,并且都没有赋值。

常见设计模式-单例模式(1)

常见设计模式-单例模式(1)

虽然线程1赋值  

常见设计模式-单例模式(1)

产生对象 @535

然后线程0也执行赋值操作

常见设计模式-单例模式(1)

可以看到 线程0覆盖之前对象实例 产生不同的对象实例为 @536

常见设计模式-单例模式(1)

随后两个线程同时打印出该对象内存地址为 一模一样,实际上是后面线程创建的对象实例覆盖了前面对象的实例。

情况三:

       将线程1全部执行完,然后产生 一个对象实例,也没有被第二个线程覆盖。

       随后才执行线程0,可以发现产生了两个不同的对象实例。

常见设计模式-单例模式(1)

解决方案 将该方法同步

常见设计模式-单例模式(1)

这时可以看到,线程1可以运行,而线程0处于监听状态,等待线程1运行完毕。

常见设计模式-单例模式(1)

线程1运行完毕后,产生一个对象实例,线程0运行到该方法时,已经有一个实例会直接返回。

虽然JDK1.6之后对synchronized 优化了不少,但是不可能避免还是会有性能问题。可能会锁住整个类。

解决方案 采用双捡锁,最多只会锁住整个方法

优化方案如下:

常见设计模式-单例模式(1)

但是该单例方式性能并不能达到很高的效率

解决方案如下:采用内部类单例模式

常见设计模式-单例模式(1)

优点:1、全程没有用到synchronized关键字,性能高。

         2、运用类加载机制,首先外部类被加载的时候,会先加载内部类,其次再次去调用getInstance() 方法时才会去加载内部类的逻辑。

        3、运用JVM底层执行逻辑,完美的避免了线程安全问题

缺点:虽然构造方法被私有了,但是还是可以被反射攻击。虽然不会刻意去攻击,但是有可能会被误用。

缺点如下:

常见设计模式-单例模式(1)

解决被反射方案如下:

常见设计模式-单例模式(1)

常见设计模式-单例模式(1)

但是还有一种方式可以破坏该单例  :序列化

如下所示:

常见设计模式-单例模式(1)

解决方案:重写readResolve()方法

该方法作用是:再反序列化读取该二进制文件时,如果有这个方法存在,那么会将值赋予该类的实例。