单例模式深入探讨

Posted 抱走攻城湿

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了单例模式深入探讨相关的知识,希望对你有一定的参考价值。

一,单例概述

定义:保证一个类在全局只有一个实例,并提供全局唯一的访问点,属于创建型。

    单例模式要求类能够有返回对象一个引用(永远是同一个)和一个获得该实例的方法(必须是静态方法)。
单例的实现主要是通过以下三个步骤:
       将类的构造方法定义为私有方法(
私有构造)。这样其他类的代码就无法通过调用该类的构造方法来实例化该类的对象,只能通过该类提供的静态方法来得到该类的唯一实例。定义一个私有的类的静态实例。提供一个公有的获取实例的静态方法。

单例模式的优点:

  • 在内存在只有一个实例,减少了内存的开销;

  • 可以避免资源的多重占用;

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

单例模式的理解能很好的加深对设计模式的设计思想,以下将会通过设计单例,并破坏的过程,不对加深单例模式的“单例”性体会。以后面试啥的碰到了,反手就是送分题



二:懒汉式

    懒汉式是在类初始加载时并不会初始化类的实例,而是在真正用的时才会初始化。下面代码是最简单的一种单例实现:

public class LazySingleton {
   private static LazySingleton lazySingleton = null;
   private LazySingleton() {
   }
   public static LazySingleton getInstance() {
       if(lazySingleton == null){  
//1
           lazySingleton = new LazySingleton();
       }
       return lazySingleton;
   }
}

这种实例是最简单也是最普遍的一种实现,在单线程不会出现问题,但是在多线程并发访问的时候就会在1处出现创建两次对象的情况,所以重新设计一种双重检验的懒汉式:

public class LazyDoubleCheckSingleton {
   private static LazyDoubleCheckSingleton lazyDoubleCheckSingleton = null;

// private volatile static
LazyDoubleCheckSingleton lazyDoubleCheckSingleton = null;

    private LazyDoubleCheckSingleton() {
   }
   public static LazyDoubleCheckSingleton getInstance() {
       if (lazyDoubleCheckSingleton == null) {
           synchronized (LazyDoubleCheckSingleton.class) {
               if (lazyDoubleCheckSingleton == null) {
                   lazyDoubleCheckSingleton = new LazyDoubleCheckSingleton();  
//2
               }
           }
       }
       return lazyDoubleCheckSingleton;
   }
}

上面的设计在对象上加了锁看似能保证了在多线程访问的时候对象的唯一性,但是在创建对象的时候也会由于指令的重排序而导致并发异常。上文中的2处,这种情况可通过idea的多线程debug尝试复现。

单例模式深入探讨

当然这种指令重排序导致的异常,我们可以在要创建的对象上添加volatile关键字解决,我们也可以通过静态内部类的方式避免:

public class StaticInnerClassSingleton {
   // 私有内部类,禁止其他访问
   
private static class InnerClass {
       private static StaticInnerClassSingleton staticInnerClassSingleton = new StaticInnerClassSingleton();
   }
   //提供单例方法
   
public static StaticInnerClassSingleton getInstance() {
       return InnerClass.staticInnerClassSingleton;
   }
   // 私有构造
   
private StaticInnerClassSingleton() {
   }
}

PS:类被立即初始化的几种情况:

    1)T是一个类,而且一个T类型的实例被创建。

    2)T是一个类,且T中声明的一个静态方法被调用。

    3)T中声明的一个静态字段被赋值。

    4)T中声明的一个静态字段被使用,而且这个字段不是一个常量字段。

   5)T是一个顶级类(Top Level Class,见Java语言规范的§7.6),而且一个断言语句(assert关键字修饰的语句)嵌套在T内部被执行。


二,饿汉式

    饿汉式是在类一加载就创建对象实例的一种方式,其一般实现方式如下:

class SimpleHungrySingleton{
   private SimpleHungrySingleton(){}
   private static final SimpleHungrySingleton singleton = new SimpleHungrySingleton();
   public static SimpleHungrySingleton getInstance(){
       return singleton;
   }
}

由于其设计特情,所以能避免多线程访问时的并发访问异常,但是,我们也是有手段找出毛病滴!我们

下面我们通过对象序列化与反序列化演示其问题所在:

HungrySingleton hungrySingleton = HungrySingleton.getInstance();
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("singleton"));
oos.writeObject(hungrySingleton);

ObjectInputStream ois = new ObjectInputStream(new FileInputStream("singleton"));
HungrySingleton newSingleton = (HungrySingleton) ois.readObject();

System.out.println(hungrySingleton);
System.out.println(newSingleton);

上面是演示代码,下面我们来看控制台的打印结果:

单例模式深入探讨

结论:反序列化会破坏单例效果。

既然出现了这种问题,我们该怎么解决呢?

其实也简单,我们可以在我们的单例类上添加下面这个方法:

// 本文章不在讲述原理,具体可跟踪对象的序列化源码,里面有所提到
public
Object readResolve(){ return hungrySingleton;}

现在各个问题都解决了,是不是我们的单例在任何条件下都能满足我们的初衷了呢? 其实我们还有终极验证手段--反射!

下面演示来了:

Class c = HungrySingleton.class;
Constructor constructor = c.getDeclaredConstructor();
constructor.setAccessible(true);

HungrySingleton newInstance = (HungrySingleton) constructor.newInstance();
HungrySingleton instance = HungrySingleton.getInstance();

System.out.println(instance);
System.out.println(newInstance);
System.out.println(instance == newInstance);

结果很明显,我们的单例类又不单例了(`ー´),这种情况我们怎么解决呢?

其实我们可以让该类禁止反射调用!

private HungrySingleton(){
   if(hungrySingleton != null){
       throw new RuntimeException("单例模式禁止反射调用!");
   }
}

终于连反射都不能揪出我们的毛病了,单例心累史·····


三, 单例最佳实践--枚举

    单例模式坑多易踩,而其最佳实践确实枚举,话不多说,我们上代码:

public enum EnumSingleton {
   INSTENCE{
       protected void method(){
           System.out.println(" 单例最佳实践 -- 枚举");
       }
   };
   protected abstract void method();
   private Object data;
   public static EnumSingleton getInstence() {
       return INSTENCE;
   }
   public Object getData() { return data; }
   public void setData(Object data) { this.data = data;}
}

无论你怎么玩,该单例就是单例!关于其如何实现单例的我们一跟其class文件就能一探究竟(枚举类禁止反射调用)

package com.cui.algorithm.demo.lazy_singleton;
public enum EnumSingleton {
   INSTENCE {
       protected void method() {
           System.out.println(" 单例最佳实践 -- 枚举");
       }
   };
   private Object data;
   private EnumSingleton() { }
   protected abstract void method();
   public static EnumSingleton getInstence() {
       return INSTENCE;
   }
   public Object getData() { return this.data; }
   public void setData(Object data) { this.data = data;}
}


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

深入Java单例模式

深入 Java 单例模式

绝对深入单例模式

Java 单例模式探讨

深入单例模式 - Java实现

Android 深入理解单例模式