单例模式
2022-05-22 本文已影响0人
世间哪有坦途
一、单例模式的定义
单例模式:私有化单例类的构造方法并提供全局生成方法以返回该类的实例对象,以保证在全局范围内,该类的实例对象仅能初始化一份,多次调用生成方法返回的是同一个实例对象。
二、单例模式的分类
-
饿汉模式:在使用之前就实例化一份对象,以保证无论何时获取该单例对象时,该对象都是实例化成功的状态,饿汉模式保证了第一次和第N次获取对象的性能是一样的。
-
懒汉模式:在第一次调用该实例对象时才会初始化该实例对象,避免无意义的对象初始化所占用的CPU资源以及内存的占用,但饿汉模式牺牲了第一次获取该实例对象时的性能(因为要初始化实例化对象),而且考虑到高并发的业务场景,往往需要通过更复杂的编码方式来保证该实例对象不会在多线程情况下被创建两次以及创建不完全就被使用的异常情况(比如双重检查锁)。
三、单例模式的实现方式
-
SpringIOC容器(singleton,懒汉式) :笔者看来,SpringIOC容器(singleton)就是最常用的单例模式的应用,SpringIOC容器通过Map方式将初始化的Bean存储起来,并通过BeanType或者BeanName的方式获取Bean对象,在Spring容器初始化的时候就保证的singleton范围的Bean的唯一性
-
双重检查锁(懒汉式) :
代码实现:
public class DCLDemo {
//禁止指令重排序,避免获取到不完整的单例对象
//保持内存可见性,创建出来的单例对象立即被刷新到主内存,其他线程读取时也会从主内存读取而不从线程内存中取数据
private static voliate DCLTestBean instance;
public static DCLTestBean getInstance() throws InterruptedException {
//第一次为空检查
if (instance == null) {
//第一次加锁防止多次创建
synchronized (DCLDemo.class) {
//再次判空以防止加锁之前线程休眠的阶段改实例对象已经被创建出来
if (instance == null) {
instance = new DCLTestBean();
System.out.println("new 了");
//耗时的初始化
TimeUnit.SECONDS.sleep(3);
instance.setPassword(123);
instance.setUsername("zhangsan");
}
}
}
return instance;
}
}
- 静态内部类(饿汉式)
public class Singleton {
private static class LazyHolder {
private static final Singleton INSTANCE = new Singleton();
}
private Singleton (){}
public static final Singleton getInstance() {
return LazyHolder.INSTANCE;
}
}
-
通过枚举方式(饿汉式)
单例对象:
public enum Singleton {
INSTANCE;
public void doSomething() {
System.out.println("doSomething");
}
调用方式:
public class Main {
public static void main(String[] args) {
Singleton.INSTANCE.doSomething();
}
}
当然还有其他的实现方式但都大同小异,这里主要说下前面的几种和枚举的区别,不管通过双重检查锁还是静态内部类,都有可能通过反射的方式破解掉,但枚举方式则不会,原因在反射机制new Instance()
的源码里:
//其中有一部分可以发现 如果是枚举类型 就会抛出这个异常
if ((clazz.getModifiers() & Modifier.ENUM) != 0)
throw new IllegalArgumentException("Cannot reflectively create enum objects");