单例模式的7种写法(解决反序列化和反射破坏单例的方法)


更多文章和资源欢迎访问:SuperCoder Blog



前言

单例模式属于创建者模式,分为两类:

饿汉式:类加载时就创建该单例对象
懒汉式:类加载时不创建,首次使用时才创建


一、饿汉式(静态变量)

/**
 * @author supercoedr
 *
 * 单例模式-饿汉式:静态变量创建单例对象
 */
public class Singleton {

    /**
     * 私有构造方法
     */
    private Singleton() {}

    /**
     * 创建类对象
     */
    private static Singleton singleton = new Singleton();

    /**
     * 对外提供静态方法获取该对象
     * @return
     */
    public static Singleton getInstance(){
        return singleton;
    }

}

二、饿汉式(静态代码块)

/**
 * @author supercoedr
 *
 * 单例模式-饿汉式:静态代码块
 */
public class Singleton {

    /**
     * 私有构造方法
     */
    private Singleton() {}

    /**
     * 声明类对象
     */
    private static Singleton singleton;

    /**
     * 在静态代码块中创建类对象
     */
    static {
        singleton = new Singleton();
    }

    /**
     * 对外提供静态方法获取该对象
     * @return
     */
    public static Singleton getInstance(){
        return singleton;
    }

}

饿汉式存在的问题:类加载时就创建对象,如果对象较大,且创建后未使用,则对于内存来说是一种浪费。

三、懒汉式(线程不安全)

/**
 * @author supercoedr
 *
 * 单例模式-懒汉式:线程不安全
 */
public class Singleton {

    /**
     * 私有构造方法
     */
    private Singleton() {}

    /**
     * 声明类对象
     */
    private static Singleton singleton;

    /**
     * 对外提供静态方法获取该对象
     * @return
     */
    public static Singleton getInstance(){
        if (singleton == null) {
            singleton = new Singleton();
        }
        return singleton;
    }

}

懒汉式(线程不安全):这种写法在第一次调用getInstance方法时(即第一次使用该对象时)创建对象,实现了懒加载的效果,相对饿汉式在特定情况下节省了内存空间。但在多线程环境下存在线程安全问题。

四、懒汉式(线程安全)

/**
 * @author supercoedr
 *
 * 单例模式-懒汉式:线程安全
 */
public class Singleton {

    /**
     * 私有构造方法
     */
    private Singleton() {}

    /**
     * 声明类对象
     */
    private static Singleton singleton;

    /**
     * 对外提供静态方法获取该对象
     * @return
     */
    public static synchronized Singleton getInstance(){
        if (singleton == null) {
            singleton = new Singleton();
        }
        return singleton;
    }

}

懒汉式(线程安全):这种写法既实现了懒加载的效果,又避免了多线程环境下出现线程安全问题。但通过分析可以发现只有初始化singleton对象时存在线程安全问题,一旦初始化完成就不存在线程安全的问题了,而这种写法对整个方法加了锁,导致即使初始化完成了,以后的每一次获取对象都要加锁解锁,影响运行效率。

五、懒汉式(双重检查锁)

/**
 * @author supercoedr
 *
 * 单例模式-懒汉式:双重检查锁
 */
public class Singleton {

    /**
     * 私有构造方法
     */
    private Singleton() {}

    /**
     * 声明类对象
     * volatile 解决可能出现的空指针异常问题,因为jvm在实例化对象时会对代码进行优化和指令重排序
     * 重排序后可能会导致调用构造方法的指令排到赋值指令后面,从而导致在多线程环境下,线程判断是否为空时,
     * 返回false,但在实际使用时对象为null,出现异常。volatile关键字可以保证可见性和有序性。
     */
    private static volatile Singleton singleton;

    /**
     * 对外提供静态方法获取该对象
     * @return
     */
    public static synchronized Singleton getInstance(){
        // 第一次判断,如果不等于null直接返回,如果为null,尝试获取锁
        if (singleton == null) {
            // 获取锁成功后,进入内层循环,再次判断是否为null,如果为null再初始化
            synchronized (Singleton.class){
                if (singleton == null){
                    singleton = new Singleton();
                }
            }
        }
        return singleton;
    }

}

懒汉式(双重检查锁):这种方式解决了单例、性能和线程安全问题,是一种比较好的单例模式写法。比较推荐。

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

/**
 * @author supercoedr
 *
 * 单例模式-懒汉式:静态内部类
 */
public class Singleton {

    /**
     * 私有构造方法
     */
    private Singleton() {}

    /**
     * 静态内部类中实例化对象
     */
    private static class SingletonHolder{
        private final static Singleton SINGLETON = new Singleton();
    }

    /**
     * 对外提供静态方法获取该对象
     * @return
     */
    public static synchronized Singleton getInstance(){
        return SingletonHolder.SINGLETON;
    }

}

懒汉式(静态内部类):这种写法对象由内部类创建,由于jvm在加载类的时候不会加载静态内部类,只有内部类的属性或方法被调用的时候才会被加载,并初始化其静态属性。静态属性由于被static修饰,所以保证了只被实例化一次,并且严格保证实例化顺序,由于实例是在类加载时由虚拟机初始化的,所以也不存在线程安全问题。因此这种写法保证了懒加载、单例、线程安全等问题。

静态内部类单例模式是一种优秀的单例模式,是开源项目中比较常用的一种单例模式。在没有加任何锁的情况下,保证了多线程下的安全,并且没有任何性能影响和空间的浪费。比较推荐。

七、枚举方式

/**
 * @author chaishuai
 * 
 * 单例模式:枚举方式(属于饿汉式)
 */
public enum Singleton {
    SINGLETON;
}

枚举方式:这种方式实现单例模式在不考虑内存的情况下是极力推荐的单例实现模式,因为枚举类型是线程安全的,并且只会装载一次,设计者充分的利用了枚举的这个特性来实现单例模式,枚举的写法非常简单,而且枚举类型是所有单例实现中唯一一种不会被破坏的单例实现模式。枚举方式属于饿汉式。

八、破坏单例的两种方式

枚举方式实现的单例不能被破坏。

  1. 反序列化破坏单例
public class Test {
      public static void main(String[] args) throws Exception {
          //将对象写入文件
          //writeObject2File();
          
          //从文件中读取对象
          Singleton s1 = readObjectFromFile();
          Singleton s2 = readObjectFromFile();
  
          //判断反序列化后的两个对象地址是否相同
          System.out.println(s1 == s2);
      }
  
      private static Singleton readObjectFromFile() throws Exception {
          //创建对象输入流
          ObjectInputStream ois = new ObjectInputStream(new FileInputStream("/User/dev/test.txt"));
          //读取Singleton对象
          Singleton instance = (Singleton) ois.readObject();
          return instance;
      }
  
      public static void writeObject2File() throws Exception {
          //获取Singleton类的对象
          Singleton instance = Singleton.getInstance();
          //创建对象输出流
          ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("/User/dev/test.txt"));
          //将对象写出到文件
          oos.writeObject(instance);
      }
  }

运行结果为false,表明反序列化破坏了单例模式。

  1. 反射破坏单例
public class Test {
      public static void main(String[] args) throws Exception {
          //获取Singleton类的Class对象
          Class clazz = Singleton.class;
          //获取Singleton类的私有无参构造对象
          Constructor constructor = clazz.getDeclaredConstructor();
          //取消访问检查
          constructor.setAccessible(true);
  
          //创建Singleton类的对象s1
          Singleton s1 = (Singleton) constructor.newInstance();
          //创建Singleton类的对象s2
          Singleton s2 = (Singleton) constructor.newInstance();
  
          //判断通过反射创建的两个Singleton对象地址是否相等
          System.out.println(s1 == s2);
      }
  }

运行结果为false,表明反射破坏了单例模式。

九、解决单例模式被破坏的方法

  1. 解决反序列化导致单例被破坏的问题
public class Singleton implements Serializable {
  
     /**
     * 私有构造方法
     */
    private Singleton() {}

    /**
     * 静态内部类中实例化对象
     */
    private static class SingletonHolder{
        private final static Singleton SINGLETON = new Singleton();
    }

    /**
     * 对外提供静态方法获取该对象
     * @return
     */
    public static synchronized Singleton getInstance(){
        return SingletonHolder.SINGLETON;
    }
      
     /**
      * 解决反序列化破坏单例模式
      * 在反序列化调用readObject方法时底层会判断是否存在readResolve方法,
      * 如果存在则调用readResolve方法。所以在readResolve方法中返回创建的单例
      * 对象即可解决反序列化导致的单例被破坏问题
      */
     private Object readResolve() {
         return SingletonHolder.SINGLETON;
     }
  }
  1. 解决反射导致单例被破坏的问题
public class Singleton {

    /**
     * 私有构造方法
     */
    private Singleton() {
		/*
         * 反射时会调用构造方法,所以在构造方法中添加判断,
         * 如果对象为null则允许调用,否则抛异常。
         * 这种方式就解决了反射导致单例被破坏的问题
         */
         if(singleton != null) {
             throw new RuntimeException();
         }
	}

    /**
     * 声明类对象
     */
    private static Singleton singleton;

    /**
     * 对外提供静态方法获取该对象
     * @return
     */
    public static synchronized Singleton getInstance(){
        if (singleton == null) {
            singleton = new Singleton();
        }
        return singleton;
    }
}

版权声明:本文为qq_45867375原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接和本声明。