1. 什么是单例模式

最常用的设计模式,单例对象的类必须保证只有一个实例存在,整个系统只能使用一个对象实例。
优点:不会频繁地创建和销毁对象,浪费系统资源。
使用场景:IO 、数据库连接、Redis 连接等。


2. 代码实现

2.1 饿汉式(静态常量和静态代码块)

饿汉式(静态常量):写法简单,类装载的时候就完成了实例化,避免了线程同步问题,没有达到 LazyLoading 的效果,如果没用这个对象,会造成资源浪费。
饿汉式(静态代码块):类装载的时候,执行静态代码块中的语句,初始化类的实例,没有达到 LazyLoading 的效果,如果没用这个对象,会造成资源浪费。

  1. public class HungerSingleton1 {
  2. private HungerSingleton1() {}
  3. private static HungerSingleton1 instance = new HungerSingleton1();
  4. public static HungerSingleton1 getInstance() {
  5. return instance;
  6. }
  7. }
  8. public class HungerSingleton2 {
  9. private HungerSingleton2() {
  10. }
  11. private static HungerSingleton2 instance;
  12. static {
  13. instance = new HungerSingleton2();
  14. }
  15. public static HungerSingleton2 getInstance() {
  16. return instance;
  17. }
  18. }

2.2 懒汉式

线程不安全:在多个线程使用同一个资源的时候,有可能存在一个资源被一个线程占有,但一系列操作(原子操作:不可再分割的操作)并未执行完成,执行过程中的资源被其他线程拿去用了。
线程不同步:举个例子来说,有变量int a=0.,同时被2个线程各加1,如果最后输出得到a的值是2,就是线程同步的。如果最后输出得到a的值只有1(少加)或者0(没加)的情况,就属于没同步。

//达到lazy loading的效果,但是只能在单线程下使用;多线程可能产生多个实例
public class LazySingleton1 {
    private LazySingleton1(){};
    private static LazySingleton1 instance;

    public static LazySingleton1 getInstance() {
        if (instance == null) {
            instance = new LazySingleton1();
        }
        return instance;
    }
}

//线程安全,同步方法,解决线程安全问题,效率低
public class LazySingleton2 {
    private LazySingleton2(){};
    private static LazySingleton2 instance;

    public static synchronized LazySingleton2 getInstance() {
        if (instance == null) {
            instance = new LazySingleton2();
        }
        return instance;
    }
}

//线程安全,同步代码块,锁住当前类,但不能起到线程同步的作用
public class LazySingleton3 {
    private LazySingleton3(){};
    private static LazySingleton3 instance;

    public static LazySingleton3 getInstance() {
        if (instance == null) {
            synchronized (LazySingleton3.class) {
                instance = new LazySingleton3();
            }
        }
        return instance;
    }
}

2.3 双重检查

双重检查:实例化代码只执行一次,后面再访问时,判断(instance==null),线程安全,延迟加载,效率较高

public class DoubleCheckSingleton {
    private DoubleCheckSingleton(){}

    private static DoubleCheckSingleton instance;

    public static DoubleCheckSingleton getInstance() {
        if (instance == null) {
            synchronized (DoubleCheckSingleton.class) {
                if (instance == null) {
                    instance = new DoubleCheckSingleton();
                }
            }
        }
        return instance;
    }
}

2.4 静态内部类

静态内部类:采用了类装载的机制来保证初始化实例只有一个线程,静态内部类在 InternalSingleton 类被装载时不会立即实例化,而是在需要实例化时,调用getInstance 方法,才会装载 SingletonInstance 类,从而完成 InternalSingleton 的实例化

  • 类的静态属性只会在第一次加载类的时候初始化,所以在这里,JVM帮助我们保证了线程的安全性,在类进行初始化时,别的线程是无法进入的。
  • 避免了线程不安全,利用静态内部类特点实现延迟加载,效率高。

    public class InternalSingleton {
      private InternalSingleton(){}
    
      private static class SingletionInstance {
          private static final InternalSingleton INSTANCE = new InternalSingleton();
      }
    
      public static InternalSingleton getInstance() {
          return SingletionInstance.INSTANCE;
      }
    }
    

    2.5 枚举

    枚举实现:不仅能避免多线程同步问题,而且还能防止反序列重新创建新的对象。

为什么枚举类可以避免多线程问题?

对枚举类进行反编译可以发现,当我们使用 enum 来定义一个枚举类型的时候,编译器会自动帮我们创建一个 final 类型的类继承 Enum类,所以枚举类型不能被继承,类中属性都使用了 static 修饰。因为 static 类型的属性会在类被加载之后被初始化,当一个 Java 类第一次被真正使用到的时候静态资源被初始化、Java 类的加载和初始化过程都是线程安全的。所以,创建一个 enum 类型是线程安全的。

public enum EnumSingletion {
    INSTANGE;
    public EnumSingletion getInstange() {
        return INSTANGE;
    }
}

参考链接:https://www.cnblogs.com/z00377750/p/9177097.html