创建型模式-> 单例模式(SingleTon)

70 阅读4分钟

创建型模式-> 单例模式(SingleTon)

什么是单例模式

单例模式属于创建型设计模式,是Java种最简单的设计模式之一。

这种模式要求一个类仅有一个实例,并且提供了一个全局的访问点。在程序中多次使用同一个对象且作用相同时,所有需要调用的地方就会共享这个访问点(对象),从而防止因频繁创建对象而损耗系统性能。

优缺点

优点:

  • 全局唯一实例:通过单例模式,可以确保一个类只有一个实例对象存在,全局范围内可以方便地访问该实例。
  • 节省资源:由于只有一个实例存在,单例模式可以节省系统资源(如内存、CPU等),避免多次创建和销毁对象的开销。
  • 避免对共享资源的竞争:在多线程环境中,单例模式可以避免对共享资源的竞争问题,确保数据一致性和线程安全性。

缺点:

  • 不支持多态:单例模式一般只能创建一个固定类型的对象实例,不支持多态的灵活性。
  • 降低了代码的灵活性:对于使用了单例模式的代码,如果需要改变实例化策略或使用其他类型的实例,可能需要修改代码和重新设计。
  • 隐藏了依赖关系:单例模式可能隐藏了对其他组件或对象的依赖关系,使得代码的结构不够清晰,增加了代码的理解和维护的难度。

应用场景

生活场景

政府是单例模式的一个很好的示例。 一个国家只有一个官方政府。 不管组成政府的每个人的身份是什么, ​ “某政府” 这一称谓总是鉴别那些掌权者的全局访问节点。

Java场景

Bean定义的默认作用域: 在Spring中,默认情况下,所有的Bean都是单例的,也就是说 Spring 容器中只会创建一个特定类型的Bean实例。
Spring容器(ApplicationContext): Spring容器本身也是一个使用了单例模式的对象。无论是基于XML配置的ClassPathXmlApplicationContext,还是基于注解的AnnotationConfigApplicationContext,它们都是单例的,只会生成一个容器实例。
Spring AOP中的切面(Aspect): 在Spring AOP中,切面是用来定义横切关注点(如日志、事务等)的类。默认情况下,Spring会将切面定义为单例,以确保在整个应用程序中只有一个切面实例,以避免创建过多的代理对象。

创建单例

饿汉式(2种)

/**
 * @author cris
 * @className HungrySingleTon
 * @description 饿汉:在类刚一初始化的时候就立即把单例对象创建出来
 * @date 2024/06/29 08:53
 **/
public class HungrySingleTon {

    private HungrySingleTon() {}

    private static HungrySingleTon instance = null;
    static {
        instance = new HungrySingleTon();
    }
    
    public static HungrySingleTon getInstance() {
        return instance;
    }
}

懒汉式(4种)

/**
 * @author cris
 * @className LazySingleTon
 * @description 懒汉:懒加载,就是在需要的时候才回去创建对象
 * @date 2024/06/29 08:57
 **/
public class LazySingleTon {

    private LazySingleTon() {}
    
    private static LazySingleTon instance;

    /**
     * 1.单例模式【线程不安全,不推荐】
     * 因为没有加锁synchronized,严格意义上不算单例。
     *
     * @return 实例
     */
    public static LazySingleTon getInstance1() {
        if (instance == null) {
            instance = new LazySingleTon();
        }
        return instance;
    }

    /**
     * 2.线程安全但效率低【不推荐】
     * 99%的情况下不需要同步
     *
     * @return 实例
     */
    public static synchronized LazySingleTon getInstance2() {
        if (instance == null) {
            instance = new LazySingleTon();
        }
        return instance;
    }

    /**
     * 3.单例模式,线程不安全【不推荐】
     * 虽然加了锁,但是等到第一个线程执行完instance2=new Singleton();跳出锁时
     * 令一个线程恰好刚判断完instance2为null,此时又会加载另一个实例
     *
     * @return 实例
     */
    public static LazySingleTon getInstance3() {
        if (instance == null) {
            synchronized (LazySingleTon.class) {
                instance = new LazySingleTon();
            }
        }
        return instance;
    }

    /**
     * 4.双重校验锁:延迟加载+线程安全【推荐】
     */
    private static volatile LazySingleTon instance4;
    public static LazySingleTon getInstance4() {
        if (instance4 == null) {
            synchronized (LazySingleTon.class) {
                if (instance4 == null) {
                    instance4 = new LazySingleTon();
                }
            }
        }
        return instance4;
    }
}

静态内部类

/**
 * @author cris
 * @className StaticInnerSingleTon
 * @description 静态内部类单例模式
 * @date 2024/06/29 09:06
 **/
public class StaticInnerSingleTon {

    private static class StaticInnerSingleTonHolder {
        private static final StaticInnerSingleTon INSTANCE = new StaticInnerSingleTon();
    }

    private StaticInnerSingleTon(){}

    public static final StaticInnerSingleTon getInstance() {
        return StaticInnerSingleTonHolder.INSTANCE;
    }
}

枚举

/**
 * @author cris
 * @className SingleTon
 * @description
 * @date 2024/06/29 23:57
 **/
public enum SingleTon {
    INSTANCE;

    public void whateverMethod() {
        System.out.println("单例模式最佳实现方式");
    }
}

总结

单例最常见的写法:懒汉式、饿汉式。
懒汉式:懒加载,在调用的时候才会实例化对象,推荐使用双重校验锁的方式。
饿汉式:类加载的时候就已经实例化好对象了,不会存在并发安全和性能问题。
在开发中,如果内存要求高,就用懒汉式,不高则用饿汉式。
最优雅的方式是使用枚举,代码极简,没有线程安全问题,又能防止反射和序列化时破坏单例。