设计模式--单例模式

单例模式在各个框架中设计或者实际开发经常遇到,主要是保证一个类只有一个示例,减少常用类频繁创建带来的性能损失。

常见的场景有:

  • Spring中的Bean
  • 项目中全局的属性
  • 数据库连接池

Static关键字

一个简单的解决方式是使用static关键字,类加载过程在可以保证没有示例的情况下也能使用类的属性和方法。

public class Singleton_00 {

    public static Map<String,String> cache = new ConcurrentHashMap<String, String>();
    
}
  • 这种方式在初始化的过程中会直接进行加载,导致启动缓慢
  • 静态的方法内部不能直接调用类里的其它方法

懒汉模式(线程不安全)

使用时创建类的示例,但是在多线程中不安全,存在多线程同时创建的风险。

public class Singleton_01 {

    private static Singleton_01 instance;

    private Singleton_01() {
    }

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

}
  • 将构造函数私有化

懒汉模式(线程安全)

对创建方法进行加锁

public class Singleton_02 {

    private static Singleton_02 instance;

    private Singleton_02() {
    }

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

}

饿汉模式(线程安全)

程序启动时直接加载创建类实例,非懒加载

public class Singleton_03 {

    private static Singleton_03 instance = new Singleton_03();

    private Singleton_03() {
    }

    public static Singleton_03 getInstance() {
        return instance;
    }

}

懒汉式-使用类的内部类(线程安全)

public class Singleton_04 {

    private static class SingletonHolder {
        private static Singleton_04 instance = new Singleton_04();
    }

    private Singleton_04() {
    }

    public static Singleton_04 getInstance() {
        return SingletonHolder.instance;
    }

}
  • 关于使用静态内部类,必须清楚一个类的加载顺序。静态方法>静态代码块>静态内部类静态代码块>静态内部类静态方法。
  • 静态属性加载时创建,而静态方法(包括静态内部类)要在创建使用时才会调用。
  • 借用JVM虚拟机保证多线程并发的正确性来确保线程安全,并且符合懒加载, 也不会因为加锁耗费性能
  • 推荐使用

懒汉模式-双重锁校验(线程安全)

和加锁的懒汉模式差不多,只是把锁放在了示例的部分,减少了获取锁的耗时。

public class Singleton_05 {

    private static volatile Singleton_05 instance;

    private Singleton_05() {
    }

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

}
  • volatile是一种轻量同步机制,可以确保变量在多线程的可见性和禁止指令重排序(分配空间、指向空间、初始化对象,顺序固定)
  • synchronized (Singleton_05.class)保证了的多个实例线程都是访问的同一个对象

懒汉模式-CAS(线程安全)

CAS(Compare and Swap)是一种用于实现无锁算法的原子操作,广泛应用于并发编程中。CAS能够保证操作的原子性。

public class Singleton_06 {
    private static final AtomicReference<Singleton_06> instance = new AtomicReference<>();

    // 私有构造函数
    private Singleton_06() { }

    public static Singleton getInstance() {
        // 尝试使用CAS原子操作设置实例
        Singleton singleton = instance.get();
        if (singleton == null) {
            singleton = new Singleton_06();
            if (instance.compareAndSet(null, singleton)) {
                return singleton;
            } else {
                return instance.get(); // 另一个线程已经创建了实例,返回它
            }
        }
        return singleton;
    }
}
  • CAS不需要加锁,所以性能高
  • 但CAS存在ABA问题和长轮询问题

饿汉式-枚举模式实现(线程安全)

Java 枚举类型本质上是线程安全的,并且提供了序列化机制,因此可以很自然地实现单例,而无需考虑线程安全或反序列化的问题。 并且所有的枚举实例在类加载时就被创建和初始化,这些实例是固定的,不能改变或被扩展。

public enum Singleton_07 {

    INSTANCE;
    // 可以定义实例方法
    public void someMethod() {
        System.out.println("Doing something in Singleton instance");
    }
}

总结

单例模式是最常用的一种模式,但包含了很多关于JAVA锁、并发、静态类、内部类、线程安全等知识。