单例模式的概念

在JVM中某个类的对象仅能有一个,而且自行实例化并向整个系bai统du提供这个实例。

如何创建单例对象

饿汉模式

  1. /**
  2. * 饿汉模式
  3. * @author zhy
  4. * @date 2021/2/2316:33
  5. */
  6. public class MySingleton1 {
  7. public static final MySingleton1 INSTANCE = new MySingleton1();
  8. private MySingleton1(){};
  9. }

私有化构造函数,则证明所有的类不能再通过new 关键字来实例化单例类。而且 static final 关键字表示在JVM加载的时候就实例化了一个对象。此种方式的优点是线程安全,调用效率高。缺点是如果没有地方用到这个单例对象,就很浪费资源。

懒汉模式

  1. /**
  2. * 懒汉模式
  3. * @author zhy
  4. * @date 2021/2/2316:33
  5. */
  6. public class MySingleton2 {
  7. private static MySingleton2 INSTANCE;
  8. private MySingleton2(){};
  9. //保证线程安全
  10. public synchronized static MySingleton2 getInstance(){
  11. if(INSTANCE == null){
  12. INSTANCE = new MySingleton2();
  13. }
  14. return INSTANCE;
  15. }
  16. }

懒汉模式则实现了调用的时候再实例化,但是为了防止并发,则在获取实例的方法上加上了 synchronized 关键字确保线程安全。这样做的优点是节省空间,但是牺牲了加载效率。

静态内部类方式

升级版的写法则是使用静态内部类的方式

  1. /**
  2. * 单例,静态内部类方式
  3. * @author zhy
  4. * @date 2021/2/2316:33
  5. */
  6. public class MySingleton3 {
  7. private MySingleton3(){};
  8. private static class MySingletonInstance{
  9. private static final MySingleton3 INSTANCE = new MySingleton3();
  10. }
  11. public static MySingleton3 getInstance(){
  12. return MySingletonInstance.INSTANCE;
  13. }
  14. }

这种方式算是比较完美的,线程安全,延迟加载都做到了。

防止反射攻击生成对象

虽然我们禁用了new 关键字创建对象,但是如果有“特权平台”获取构造器,再通过构造器生成对象,则单例模式就被破坏了

饿汉模式

例如看个例子,以上面的饿汉模式为例:
image.png
结果输出,可见并不是同一个对象
image.png
所以我们可以在构造方法中加入一个判读,如果对象已经被创建则抛出异常

  1. /**
  2. * 饿汉模式
  3. * @author zhy
  4. * @date 2021/2/2316:33
  5. */
  6. public class MySingleton1 {
  7. public static final MySingleton1 INSTANCE = new MySingleton1();
  8. private MySingleton1(){
  9. if (INSTANCE != null){
  10. throw new IllegalStateException();
  11. }
  12. };
  13. }

测试:
image.png

懒汉模式

如果是懒汉模式,则上面的做法还是存在风险的,如果反射发生在getInstance()之前,那么 INSTANCE != null 判断不成立,则不会抛异常,所以还是可以通过反射生成对象。

静态内部类方式

/**
 * 单例,静态内部类方式
 * @author zhy
 * @date 2021/2/2316:33
 */
public class MySingleton3 {

    private MySingleton3(){
        if (MySingletonInstance.INSTANCE != null){
            throw new IllegalStateException();
        }
    };

    private static class MySingletonInstance{
        private static final MySingleton3 INSTANCE = new MySingleton3();
    }

    public static MySingleton3 getInstance(){
        return MySingletonInstance.INSTANCE;
    }

}

反序列化创建实例

如果类实现了Serializable接口,则类可以通过反序列化来实例化,单例模式为了防止反序列化目标类的对象,则需要在目标类中添加一个 private Object readResolve()方法
以饿汉模式为例

/**
 * 饿汉模式
 * @author zhy
 * @date 2021/2/2316:33
 */
public class MySingleton4 implements Serializable{

    private static final MySingleton4 INSTANCE = new MySingleton4();

    private MySingleton4(){
        if (INSTANCE != null){
            throw new IllegalStateException();
        }
    }

    public MySingleton4 getInstance(){
        return INSTANCE;
    }

    private Object readResolve(){
        return INSTANCE;
    }
}

添加readResolve() 方法的原因就是JVM在反序列化组装对象的时候,就会在目标对象中合成一个readResolve()方法,通过readResolve()返回一个对象。我们这里重写了直接返回 单例对象,防止反序列化对单例模式进行破坏。