一、作用
单个实例
保证我们的实例对象在整个应用程序中只有一个实例
二、创建方式
五种实现方式
2.1 饿汉式
在类加载的时候立即初始化,并且创建单例对象
它绝对线程安全,在线程出现以前就实例化了,不可能存在访问安全问题
优点:不加任何锁,执行效率比较高,用户体验比懒汉单例模式好
缺点:浪费内存,不管用不用都占着内存
publicclassHungrySingleton { // 直接实例化方式// private static final HungrySingleton hungrySingleton = new HungrySingleton();privatestaticfinalHungrySingletonhungrySingleton; // 静态块单例模式static { hungrySingleton=newHungrySingleton(); } privateHungrySingleton(){} privatestaticHungrySingletongetInstance(){ returnhungrySingleton; } }
2.2 懒汉式
在外部调用的时候才进行实例化,相比较饿汉式避免了资源浪费
在单线程情况下,比较友好。
多线程情况下,会存在线程安全问题
publicclassLazySimpleSingleton { privateLazySimpleSingleton() { } privatestaticLazySimpleSingletonlazySimpleSingleton=null; publicstaticLazySimpleSingletongetInstance() { if (null==lazySimpleSingleton) { lazySimpleSingleton=newLazySimpleSingleton(); } returnlazySimpleSingleton; } }
2.3 双重检测锁(DCL)
基于懒汉式的线程安全问题,有了双重校验锁
publicclassLazyDoubleCheckSingleton { privatevolatilestaticLazyDoubleCheckSingletonlazyDoubleCheckSingleton=null; /*** 私有化构造方法,防止直接通过new实例化*/privateLazyDoubleCheckSingleton() { } publicstaticLazyDoubleCheckSingletongetInstance() { if (lazyDoubleCheckSingleton==null) { synchronized (LazyDoubleCheckSingleton.class) { if (lazyDoubleCheckSingleton==null) { // 1.分配内存空间 2.执行构造方法,实例化对象 3.把这个对象赋给这个空间// 不加volatile关键字,会造成指令重排,1,3,2lazyDoubleCheckSingleton=newLazyDoubleCheckSingleton(); } } } returnlazyDoubleCheckSingleton; } }
2.4 静态内部类
publicclassStaticSingleton { publicstaticclassInnerStaticSingleton { /*** 声明外部类型的静态常量*/publicstaticfinalStaticSingletoninstance=newStaticSingleton(); } privateStaticSingleton() { } publicStaticSingletongetInstance() { returnInnerStaticSingleton.instance; } }
2.5 枚举类型
publicenumEnumSingleton { INSTANCE; publicvoidhandleMethod(){ // 业务处理 } }
综上的五种写法,大多都是在考虑着线程安全问题
2.6 反射爆破问题
私有的构造器,可以通过反射去破坏。
在私有构造器中进行判断,进而抛出异常。
publicstaticvoidmain(String[] args) throwsNoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException { LazySimpleSingletoninstance=LazySimpleSingleton.getInstance(); Class<LazySimpleSingleton>clazz=LazySimpleSingleton.class; Constructor<LazySimpleSingleton>constructor=clazz.getDeclaredConstructor(); constructor.setAccessible(true); LazySimpleSingletoninstance1=constructor.newInstance(); System.out.println(instance); System.out.println(instance1); } // 结果com.example.validated.design.singleton.LazySimpleSingletoncom.example.validated.design.singleton.LazySimpleSingleton
2.7 序列化与反序列化破坏单例
LazySimpleSingleton要实现Serializable序列化接口
publicstaticvoidmain(String[] args) throwsNoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException, IOException, ClassNotFoundException { LazySimpleSingletoninstance=LazySimpleSingleton.getInstance(); Class<LazySimpleSingleton>clazz=LazySimpleSingleton.class; Constructor<LazySimpleSingleton>constructor=clazz.getDeclaredConstructor(); constructor.setAccessible(true); LazySimpleSingletoninstance1=constructor.newInstance(); System.out.println(instance); System.out.println(instance1); ObjectOutputStreamoutputStream=newObjectOutputStream(newFileOutputStream("d:/tools/a.txt")); outputStream.writeObject(instance); outputStream.flush(); outputStream.close(); ObjectInputStreaminputStream=newObjectInputStream(newFileInputStream("d:/tools/a.txt")); LazySimpleSingletoninstance2= (LazySimpleSingleton) inputStream.readObject(); inputStream.close(); System.out.println(instance); System.out.println(instance2); } // 结果com.example.validated.design.singleton.LazySimpleSingletoncom.example.validated.design.singleton.LazySimpleSingletoncom.example.validated.design.singleton.LazySimpleSingletoncom.example.validated.design.singleton.LazySimpleSingleton
我们需要重写readResolve()方法
privateObjectreadResolve() { returnlazySimpleSingleton; }
结果:
com.example.validated.design.singleton.LazySimpleSingletoncom.example.validated.design.singleton.LazySimpleSingletoncom.example.validated.design.singleton.LazySimpleSingletoncom.example.validated.design.singleton.LazySimpleSingleton
说明:readResolve()方法是基于回调的,反序列化时,如果定义了readResolve()则直接返回此方法指定的对象,而不需要再创建新的对象。
三、应用
在框架中看到的单例模式
- Spring中的Bean对象,默认是单例模式
- 相关的工厂对象都是单例,如:Mybatis中的SqlSessionFactory,Spring中BeanFactory
- 保存相关配置信息的都是单例,如:Mybatis中的Configuration对象,SpringBoot中的各个xxxAutoConfiguration对象
- 应用程序的日志应用,一般都会通过单例来实现
- 数据库的连接池的设计也是单例模式