![](http://www.eleven-smile.com/usr/uploads/2022/04/334915659.png)

一、定义

单例模式(Singleton Pattern)是指确保一个类在任何情况下都绝对只有一个示例,并提供一个全局访问点。单例模式属于创建型模式

二、应用场景

现实生活中的如:公司CEO、部门经理等。 J2EE标准中的如:ServletContext、ServletContextConfig等、Spring框架应用中的ApplicationContext、数据库的连接池等,这些都是单例模式

三、单例模式的常见写法

1、饿汉式单例

public class HungrySingleton {

    //private 对外不提供
    //static  类加载时已经获取对象
    //final   不管通过哪种方式 如反射等 不允许有多个值,保证单例的唯一性
    //此处也可以使用静态代码块方式 实现饿汉式单例模式
    private static final HungrySingleton HUNGRY_SINGLETON = new HungrySingleton();
    //私有化
    private HungrySingleton(){};

    public static HungrySingleton getInstance(){
        return HUNGRY_SINGLETON;
    }


}

优点: 线程安全,执行效率高。

缺点: 由于类加载时就初始化,不管用与不用,都会占用空间,浪费内存,是一种以时间换区空间的效果。

注意点: 如果项目少量这些类可以使用,但是如果较多,则不建议,如Spring容器就没有采用这种方式。

2、懒汉式(同步锁)

public class LazySimpleSingleton {
    private static LazySimpleSingleton lazySimpleSingleton = null;

    private LazySimpleSingleton() {

    }

    //synchronized 具有性能问题
    public synchronized static LazySimpleSingleton getInstance() {
        if (lazySimpleSingleton == null)
            lazySimpleSingleton = new LazySimpleSingleton();
        return lazySimpleSingleton;
    }
}

优点: 需要时再初始化,节省资源。线程安全。

缺点: 需要加锁synchronized(而且锁作用域较高,属于类锁),影响效率。

注意点: 这里代码加了synchronized,如果没有加,则会有线程不安全的情况,如:多线程情况获取的类的实例是不相等的。

3、懒汉式(DCL)

public class LazyDoubleCheckSingleton {
    //双重检查锁  会牵扯CPU层面的指令重排序  所以需要volatile
    private volatile static LazyDoubleCheckSingleton lazyDoubleCheckSingleton = null;

    private LazyDoubleCheckSingleton() {

    }
    //较之LazySimpleSingleton 适中的方法
    public static LazyDoubleCheckSingleton getInstance() {
        //第一次 判null 减少锁的竞争  检查是否要阻塞
        if (lazyDoubleCheckSingleton == null)
            //synchronized的作用域区别  这里是代码块 LazySimpleSingleton是类锁
            synchronized (LazyDoubleCheckSingleton.class){
                //第二次 判null 是否要创建实例
                if(lazyDoubleCheckSingleton == null)
                    lazyDoubleCheckSingleton = new LazyDoubleCheckSingleton();
            }
        return lazyDoubleCheckSingleton;
    }
}

优点: 经过双重判null处理以及synchronized作用域降级(由类锁变为代码块锁),提升性能。含有懒汉式的优点。

缺点: 代码复杂,切记需要通过validate关键字修饰lazyDoubleCheckSingleton变量,达到禁止指令重排序的效果。

注意点: lazyDoubleCheckSingleton = new LazyDoubleCheckSingleton();这段代码,其实在JVM层面已经转换为多条指令。

  • 指令1 分配对象的内存空间
  • 指令2 初始化对象
  • 指令3 设置lazyDoubleCheckSingleton指向刚分配的内存地址

经过重排序后如下:

  • 指令1 分配对象的内存空间

  • 指令2 设置lazyDoubleCheckSingleton指向刚分配的内存地址,此时对象还没被初始化

  • 指令3 初始化对象 两次判null 意义不同,第一次 判null 减少锁的竞争 检查是否要阻塞,第二次 判null 是否要创建实例。

    4、懒汉式(静态内部类)

    public class LazyInnerClassSingleton {
    
      //防止创建多次,如:反射破坏单例时
      private LazyInnerClassSingleton() {
          if (LazyInnerInnerHungry.LAZY != null) {
              throw new RuntimeException("不允许创建多个实例");
          }
      }
    
      //外部调用时才执行  属于懒汉式
      public static final LazyInnerClassSingleton getInstance() {
          return LazyInnerInnerHungry.LAZY;
      }
    
      //属于饿汉式  默认不加载
      //内部类一定是要在方法调用之前初始化,巧妙地避免了线程安全问题
      private static class LazyInnerInnerHungry {
          private static final LazyInnerClassSingleton LAZY = new LazyInnerClassSingleton();
      }
    }

    优点: 既没有synchronized关键字带来的效率问题,也是一种懒加载机制的单例模式,节省资源,用时再初始化。

缺点: 能够被反射破坏(私有化构造方法添加判断后无该问题),代码看起来怪怪的,不优雅。

注意点: LazyInnerClassSingleton.getInstance方法在调用时才去执行,是懒汉式的。 因为类加载机制,LazyInnerClassSingleton.class执行getInstance方法时才去加载LazyInnerClassSingleton$LazyInnerInnerHungry.class的创建实例的方法。 因为可以通过反射破坏,为了解决该问题,在私有化构造方法内,添加判断是否已经创建实例,若创建则抛异常,这样可解决反射破坏问题。

5、注册式单例模式-枚举登记

public enum EnumSingleton {
    INSTANCE;
    private Object data;

    public Object getData() {
        return data;
    }

    public void setData(Object data) {
        this.data = data;
    }

    public static EnumSingleton getInstance(){
        return INSTANCE;
    }
}

优点: 代码简洁、简单,线程安全,从JDK层面已经避免了反射或序列化和反序列化破坏。从代码层面是一种最优雅的单例模式。

缺点: 因为也属于饿汉式单例,故有饿汉式单例的问题,即单例创建时机不可控制。

6、注册式单例模式-容器缓存

public class ContainerSingleton {
    //容器
    private static Map<String,Object> ioc = new ConcurrentHashMap<>();

    private ContainerSingleton(){

    }

    public static Object getBean(String className){
        //类不在该容器中 通过反射找到该类,并且放到容器中
        if(!ioc.containsKey(className)){
            synchronized (ContainerSingleton.class){
                if(!ioc.containsKey(className)) {
                    Object o = null;
                    try {
                        o = Class.forName(className).newInstance();
                        ioc.put(className, o);
                    } catch (Exception e) {
                        e.printStackTrace();
                    }
                    return o;
                }else{
                    //容器中含有该类,直接返回。
                    return ioc.get(className);
                }
            }
        }else {
            //容器中含有该类,直接返回。
            return ioc.get(className);
        }
    }
}

优点: 当实例对象过多时,为了方便统一管理,可使用容器缓存方式管理。懒加载的方式

缺点: 线程不安全。需要加锁。

7、伪单例模式

public class ThreadLocalSingleton {


    private ThreadLocalSingleton(){

    }

    private static final ThreadLocal<ThreadLocalSingleton> threadLocalInstance = new ThreadLocal<ThreadLocalSingleton>(){
        @Override
        protected ThreadLocalSingleton initialValue() {
            return new ThreadLocalSingleton();
        }
    };

    public static ThreadLocalSingleton getInstance(){
        return threadLocalInstance.get();
    }
}

优点: 天生线程安全

缺点: 唯一性的作用域局限性,仅仅局限于同一线程。

四、破坏案例

1、反射

1.1 比如对懒汉式(静态内部类)案例进行反射破坏

public class LazyInnerClassSingletonTest {

    public static void main(String[] args) {
        try {
            //通过反射破坏
            Class<?> clazz = LazyInnerClassSingleton.class;
            //通过反射拿到私有的构造方法
            Constructor constructor = clazz.getDeclaredConstructor();
            //赋予访问权限,强制访问
            constructor.setAccessible(true);
            //暴力初始化
            Object o1 = constructor.newInstance();

            //再次初始化
            Object o2 = constructor.newInstance();

            System.out.println(o1);
            System.out.println(o2);
            System.out.println(o1 == o2);
        } catch (Exception e) {
            e.printStackTrace();
        }

    }
}

结论:当使用反射破坏懒汉式(静态内部类)时,需要在构造方法中判断是否已经初始化,来解决反射破坏

1.2 比如对注册式单例模式-枚举登记案列进行反射破坏

public class EnumSingletonTest {
    public static void main(String[] args) {
        try {
            Class clazz = EnumSingleton.class;
            Constructor c = clazz.getDeclaredConstructor();
            c.setAccessible(true);
            Object o = c.newInstance();
            System.out.println(o);
        } catch (Exception e) {
            e.printStackTrace();
        }

    }
}

运行报错

java.lang.NoSuchMethodException: com.devilyuan.pattern.singleton.register.EnumSingleton.<init>()
    at java.lang.Class.getConstructor0(Class.java:3082)
    at java.lang.Class.getDeclaredConstructor(Class.java:2178)

发现是无参构造方法报错,怀疑可能是只能调用有参构造方法。于是通过jad 下载了反编译软件,使用jad EnumSingleton.class

public final class EnumSingleton extends Enum
{

    public static EnumSingleton[] values()
    {
        return (EnumSingleton[])$VALUES.clone();
    }

    public static EnumSingleton valueOf(String name)
    {
        return (EnumSingleton)Enum.valueOf(com/devilyuan/pattern/singleton/register/EnumSingleton, name);
    }
    private EnumSingleton(String s, int i)
    {
        super(s, i);
    }

    public Object getData()
    {
        return data;
    }

    public void setData(Object data)
    {
        this.data = data;
    }

    public static EnumSingleton getInstance()
    {
        return INSTANCE;
    }

    public static final EnumSingleton INSTANCE;
    private Object data;
    private static final EnumSingleton $VALUES[];

    static 
    {
        INSTANCE = new EnumSingleton("INSTANCE", 0);
        $VALUES = (new EnumSingleton[] {
            INSTANCE
        });
    }
}

经反编译后的代码,原来enum类都要继承Enum,并且只有有参构造方法,这里我将原来的无参构造方法改为有参构造方法。

public class EnumSingletonTest {
    public static void main(String[] args) {
        try {
            Class clazz = EnumSingleton.class;
            //这里无参构造方法改为有参构造方法
            Constructor c = clazz.getDeclaredConstructor(String.class,int.class);
            c.setAccessible(true);
            Object o = c.newInstance();
            System.out.println(o);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

依然报错:

java.lang.IllegalArgumentException: Cannot reflectively create enum objects
    at java.lang.reflect.Constructor.newInstance(Constructor.java:417)

这个错误和在懒汉式(静态内部类)构造方法中判断是否已经初始化,来解决反射破坏有异曲同工之妙,只不过后者是用户自定义,前者是官方定义

根据错误信息探寻源码:

if ((clazz.getModifiers() & Modifier.ENUM) != 0)
            throw new IllegalArgumentException("Cannot reflectively create enum objects");
        ConstructorAccessor ca = constructorAccessor;   // read volatile

标识:修饰符是枚举不可以反射。 探寻源码2:

//从容器中获取
public static <T extends Enum<T>> T valueOf(Class<T> enumType,
                                                String name) {
        T result = enumType.enumConstantDirectory().get(name);
        if (result != null)
            return result;
        if (name == null)
            throw new NullPointerException("Name is null");
        throw new IllegalArgumentException(
            "No enum constant " + enumType.getCanonicalName() + "." + name);
    }
//容器
Map<String, T> enumConstantDirectory() {
        if (enumConstantDirectory == null) {
            T[] universe = getEnumConstantsShared();
            if (universe == null)
                throw new IllegalArgumentException(
                    getName() + " is not an enum type");
            Map<String, T> m = new HashMap<>(2 * universe.length);
            for (T constant : universe)
                m.put(((Enum<?>)constant).name(), constant);
            enumConstantDirectory = m;
        }
        return enumConstantDirectory;
    }
    private volatile transient Map<String, T> enumConstantDirectory = null;

结论:枚举登记从JAVA层面已经解决了反射破坏的问题,并且从结构层面解决了多线程的问题,因为枚举在类声明的时候已经创建好了放到了一个Map容器中

2、实现序列化接口Serializable通过反序列化破坏

2.1 比如饿汉式单例(实现了序列化接口Serializable)进行反序列化破坏

序列化 就是把内存中对象的状态转换为字节码的形式,把字节码通过IO输出流,写到磁盘上永久的保存下来,称之为持久化。

反序列化 将赤计划的字节码内容,通过IO输入流读到内存中来,转化为一个JAVA对象

public class SerializableSingleton implements Serializable {
    private final static SerializableSingleton SERIALIZABLE_SINGLETON = new SerializableSingleton();

    private SerializableSingleton(){}

    public static SerializableSingleton getInstance(){
        return SERIALIZABLE_SINGLETON;
    }
}
public class SerializableSingletonTest {
    public static void main(String[] args) {

        SerializableSingleton serializableSingleton = null;

        SerializableSingleton serializableSingleton2 = SerializableSingleton.getInstance();

        FileOutputStream fileOutputStream = null;
        try {
            fileOutputStream = new FileOutputStream("SerializableSingleton.obj");
            ObjectOutputStream objectOutputStream = new ObjectOutputStream(fileOutputStream);
            objectOutputStream.writeObject(serializableSingleton2);
            objectOutputStream.flush();
            objectOutputStream.close();

            FileInputStream fileInputStream = new FileInputStream("SerializableSingleton.obj");
            ObjectInputStream objectInputStream = new ObjectInputStream(fileInputStream);
            serializableSingleton = (SerializableSingleton) objectInputStream.readObject();
            objectInputStream.close();

            System.out.println(serializableSingleton);
            System.out.println(serializableSingleton2);

            System.out.println(serializableSingleton == serializableSingleton2);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

分析: serializableSingleton赋值null, serializableSingleton2赋值初始化,然后serializableSingleton2写入到磁盘中SerializableSingleton.obj文件(此过程为序列化),然后再从该文件读取出来,赋值给serializableSingleton(反序列化),最终比较两者。 若相等则是单例,反之则不为。

输出结果为false,很显然破坏了单例。

将代码调整,解决序列化反序列化破坏单例的问题

public class SerializableSingleton implements Serializable {
    private final static SerializableSingleton SERIALIZABLE_SINGLETON = new SerializableSingleton();

    private SerializableSingleton(){}

    public static SerializableSingleton getInstance(){
        return SERIALIZABLE_SINGLETON;
    }
    //反序列化
    public Object readResolve(){
        return SERIALIZABLE_SINGLETON;
    }
}

分析: 较之调整前的差异,只不过在该类添加了readResolve()方法,注意此处必须要写成这种方式。这里其实也是用到了桥接模式。 探寻源码: 在反序列化代码objectInputStream.readObject()中调用Object obj = readObject0(type, false);进入readObject0(type, false);方法。

case TC_ENUM:
if (type == String.class) {
throw new ClassCastException("Cannot cast an enum to java.lang.String");
}
return checkResolve(readEnum(unshared));
case TC_OBJECT:
if (type == String.class) {
throw new ClassCastException("Cannot cast an object to java.lang.String");
}
return checkResolve(readOrdinaryObject(unshared));

注意这里TC_ENUMTC_OBJECT,前者是对枚举,后者是对该饿汉式案例。

根据TC_ENUM进入readEnum(unshared)

Enum<?> en = Enum.valueOf((Class)cl, name);
result = en;

这里发现,枚举其实是通过类名和类对象类找到一个唯一的的枚举对象,所以也就不存在多次的可能性,故而枚举不能被序列化和反序列化。

根据TC_OBJECT进入readOrdinaryObject(unshared)

if (obj != null &&
            handles.lookupException(passHandle) == null &&
            desc.hasReadResolveMethod())
        {
            Object rep = desc.invokeReadResolve(obj);
            if (unshared && rep.getClass().isArray()) {
                rep = cloneArray(rep);
            }
            if (rep != obj) {
                // Filter the replacement object
                if (rep != null) {
                    if (rep.getClass().isArray()) {
                        filterCheck(rep.getClass(), Array.getLength(rep));
                    } else {
                        filterCheck(rep.getClass(), -1);
                    }
                }
                handles.setObject(passHandle, obj = rep);
            }
        }

        return obj;

这里就是先判断hasReadResolveMethod然后调用invokeReadResolve方法就是反射调用readResolve,最后将结果对象返回。这就意味着不会再重新创建对象。

3、多线程

比如上述提到的懒汉式(DCL)以及加锁的案例。都必须添加synchronized保证线程安全性。

4、指令重排序

比如上述提到的懒汉式(DCL)案例中lazyDoubleCheckSingleton = new LazyDoubleCheckSingleton();这段代码,其实在JVM层面已经转换为多条指令。

  • 指令1 分配对象的内存空间
  • 指令2 初始化对象
  • 指令3 设置lazyDoubleCheckSingleton指向刚分配的内存地址

经过重排序后如下:

  • 指令1 分配对象的内存空间
  • 指令2 设置lazyDoubleCheckSingleton指向刚分配的内存地址,此时对象还没被初始化
  • 指令3 初始化对象

故而添加volatile来阻止指令重排序带来的单例破坏影响。

5、实现Cloneable接口通过深克隆破坏

因为深克隆相当于重新new了。要解决只需要禁止深克隆即可。

5.1 单例类不实现Cloneable接口

public class Prototype implements Serializable{
}

5.2 重写clone()方法,在clone方法中返回单例对象即可。

protected Object clone() throws CloneNotSupportedException {
        return INSTANCE;
    }

6、通过不私有化构造方法破坏单例

单例类的构造方法没有私有化,那么就可以通过new创建。