单例模式(Singleton Pattern)

单例模式(Singleton Pattern)

定义

是指确保一个类在任何情况下都绝对只有一个实例,并提供一个全局访问点。

隐藏起所有的构造方法。

属于创建型模式。


适用场景

确保任何情况下都绝对只有一个实例。

比如数据库连接池、配置文件读取、缓存等。


标准示例

1. 饿汉式单例

在单例类首次加载时,就创建实例。

  • 优点:执行效率高,性能高,没有锁
  • 缺点:某些情况下,可能会有内存浪费(系统中单例类很多的时候,无论是否使用,加载时都会初始化到内存)
java 复制代码
public class HungrySingleton {
    //静态成员变量
    private static final HungrySingleton INSTANCE;
    //代码块对成员变量赋初始值
    static {
        INSTANCE = new HungrySingleton();
    }

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

    //全局访问方法
    public static HungrySingleton getInstance(){
        return INSTANCE;
    }
}

2. 懒汉式单例

被外部类调用时才会创建实例。

  • 优点:节省了内存
  • 缺点:线程不安全问题
java 复制代码
public class LazySingleton {
    private static LazySingleton instance;

    private LazySingleton(){}

    public static LazySingleton getInstance(){
        if(instance == null){
            // 位置A
            instance = new LazySingleton();
            // 位置B
        }
        // 位置C
        return instance;
    }
}

上述代码的线程不安全体现在两种情况:

两个线程输出实例相同,实际上是后者覆盖了前者:

有两个线程,都执行到了位置B,这时后者会覆盖掉前者,当线程继续到位置C时,它们输出的确是同一个实例 ------把前者覆盖掉的那个实例。

两个线程同时进入非空判断条件中,然后按顺序返回,输出两个不同实例:

有两个线程,一个执行到了位置A,另一个执行到了位置B,然后位置B的线程执行到位置C,输出它的实例;位置A的也随后执行到位置B,创建了新的实例覆盖原有实例,然后执行到位置C输出该新实例。两次输出的是不同的实例

解决懒汉式单例线程安全问题的方法一:加锁

java 复制代码
public class LazySingleton {
    private static LazySingleton instance;
    
    private LazySingleton(){
    }

	//加锁解决线程安全问题
    public synchronized static LazySingleton getInstance(){
        if(instance == null){
            instance = new LazySingleton();
        }
        return instance;
    }
}

但是,这种方式会有副作用,所有的获取实例的调用,都会受到synchronized的性能影响。

解决懒汉式单例线程安全问题的方法二:双重检查

java 复制代码
public class DoubleCheckLazySingleton {
	//加 volatile 是为了解决指令重排序的问题,保证instanc变量先创建
    private volatile static DoubleCheckLazySingleton instance;

    private DoubleCheckLazySingleton(){
    }

    public static DoubleCheckLazySingleton getInstance(){
        //第一次检查,目的是要判断是否要加锁
        if(instance == null){
            synchronized (DoubleCheckLazySingleton.class){
                //第二次检查,目的是判断是否要创建实例
                if(instance == null) {
                    instance = new DoubleCheckLazySingleton();
                }
            }
        }
        return instance;
    }
}

上述代码可以解决问题,只是可读性不够高。

解决懒汉式单例线程安全问题的方法三:静态内部类

利用原理:内部类只有在调用时才会被加载。

java 复制代码
public class StaticInnerClassLazySingleton {
    private StaticInnerClassLazySingleton(){}

    public static StaticInnerClassLazySingleton getInstance(){
        return LazyInnerClass.INSTANCE;
    }

    private static class LazyInnerClass{
        private static final StaticInnerClassLazySingleton INSTANCE = new StaticInnerClassLazySingleton();
    }
}

3. 注册式单例

将每一个实例都缓存到统一的容器中,使用唯一标识获取实例

java 复制代码
public class ContainerSingleton {
    private ContainerSingleton(){}

    private static ContainerSingleton instance;

    private static Map<String,Object> ioc = new ConcurrentHashMap<String, Object>();

    public static Object getInstance(String className){
        Object instance = null;
        if(!ioc.containsKey(className)){
            try {
                instance = Class.forName(className).newInstance();
                ioc.put(className,instance);
            } catch (Exception e) {
                e.printStackTrace();
            }
            return  instance;
        }else{
            return ioc.get(className);
        }
    }
}

以上就是 单例模式 的全部内容,感谢阅读。

相关推荐
Shimiy18 分钟前
第六章 面向对象编程------进阶
java
大葱白菜22 分钟前
Java Set 集合详解:从基础语法到实战应用,彻底掌握去重与唯一性集合
java·后端
大葱白菜23 分钟前
Java Map 集合详解:从基础语法到实战应用,彻底掌握键值对数据结构
java·后端
添乱23 分钟前
「Java案例」判断是否是闰年的方法
java
FG.27 分钟前
Day22
java·面试
菜鸟的迷茫29 分钟前
Redis 缓存雪崩、穿透、击穿面试题深度解析与 Spring Boot 实战代码示例
java
珹洺40 分钟前
C++算法竞赛篇:DevC++ 如何进行debug调试
java·c++·算法
SHUIPING_YANG1 小时前
根据用户id自动切换表查询
java·服务器·数据库
爱吃烤鸡翅的酸菜鱼1 小时前
IDEA高效开发:Database Navigator插件安装与核心使用指南
java·开发语言·数据库·编辑器·intellij-idea·database
惊涛骇浪、1 小时前
SpringMVC + Tomcat10
java·tomcat·springmvc