04-01-设计模式 单利模式
单利模式介绍
简介
说白了, 单利模式, 从字面上就能理解, 就是采取一定的方法保证在整个系统中, 对某个类只能存在一个对象实例,并且该类只提供一个取得其对象实例的方法(静态方法)
举例
比如Hibernate的SessionFactory,它充当数据存储源的代理, 并负责创建Session对象, SessionFactory并不是轻量级的, 一般情况下, 一个项目通常只需要一个SessionFactory就够了,这时就会使用到单利模式
Hibernate可能现在基本用的不多了,但是Spring大家应该都用, 在Spring中, 一般声明的Bean, 如果没有特殊配置, 那么它就是单利的
单利模式实现的八种方式
- 饿汉式(静态常量)
- 饿汉式(静态代码块)
- 懒汉式(线程不安全)
- 懒汉式(线程安全, 同步方法)
- 懒汉式(线程安全, 同步代码块)
- 双重检查
- 静态内部类
- 枚举
饿汉式(静态常量)
步骤
- 私有化构造方法(防止 new)
- 类的内部创建对象
- 向外部暴露一个静态的公共方法, getInstance
代码
package com.dance.design.designmodel.simpleinterestmodel; public class SimpleOne { } /** * 饿汉式(静态常量) */ class SingleOne{ // 创建静态常量 private static final SingleOne singleOne = new SingleOne(); // 私有化构造 private SingleOne(){ } /** * 提供一个公共静态方法 * @return 类 */ public static SingleOne getInstance() { return singleOne; } }
我感觉这没啥好测试的, 我下面就直接写代码了, 但凡做过开发的,应该都看的懂
总结
- 优点
- 写法简单, 线程安全
- 缺点
- 不是懒加载, 如果不用的话, 就会造成内存浪费
- 为什么线程安全?
- 基于类加载实现的, 存放于元空间, 应为是static的 在类加载的时候就会创建
- 为什么不用,会造成内存浪费?
- 应为在类加载的时候就创建, 不是用的时候才创建, 所以不是懒加载, 应为是一上来就加载, 并且是放在元空间的, 并不会被垃圾回收器回收, 如果不用就一直 存在, 所以会造成内存浪费
- 结论
- 如果是一定会用到, 可以用, 但是还是建议使用懒加载的
饿汉式(静态代码块)
步骤
- 私有化构造
- 声明静态成员
- 静态代码块初始化
- 对外提供公共静态方法
代码
package com.dance.design.designmodel.simpleinterestmodel; public class SimpleTwo { } /** * 饿汉式(静态代码块) */ class SingleTwo{ // 创建静态常量 private static final SingleTwo singletwo; static { singletwo = new SingleTwo(); } // 私有化构造 private SingleTwo(){ } /** * 提供一个公共静态方法 * @return 类 */ public static SingleTwo getInstance() { return singletwo; } }
总结
其实和静态常量方式,一样, 就是从直接New改到了代码块中
懒汉式(线程不安全)
步骤
- 私有化构造
- 声明静态成员变量
- 提供对外公共方法
- 在公共方法中去创建对象
代码
package com.dance.design.designmodel.simpleinterestmodel; public class SimpleThree { } /** * 懒汉式(线程不安全) */ class SingleThree{ // 创建静态常量 private static SingleThree singleThree; // 私有化构造 private SingleThree(){ } /** * 提供一个公共静态方法 * @return 类 */ public static SingleThree getInstance() { if (null == singleThree) { singleThree = new SingleThree(); } return singleThree; } }
总结
- 优点
- 提供了懒加载
- 缺点
- 线程不安全
- 为什么说提供了懒加载?
- 应为并没有在类加载的时候就创建, 而是在第一次调用的时候才创建的
- 为什么说线程不安全
- 应为没有锁机制, 导致多个线程可能同时进入到if块的内部, 导致都创建了对象,导致多利的存在, 破坏了单利模式的存在
- 总结
- 不要用, 不为啥
懒汉式(线程安全, 同步方法)
步骤
- 私有化构造
- 声明静态成员
- 提供对外公共静态方法
- 在方法中创建对象
- 在方法上加内置锁
代码
package com.dance.design.designmodel.simpleinterestmodel; public class SimpleFour { } /** * 懒汉式(线程安全, 同步方法) */ class SingleFour{ // 创建静态常量 private static SingleFour singleFour; // 私有化构造 private SingleFour(){ } /** * 提供一个公共静态方法 * @return 类 */ public synchronized static SingleFour getInstance() { if (null == singleFour) { singleFour = new SingleFour(); } return singleFour; } }
总结
- 优点
- 懒加载
- 线程安全
- 缺点
- 效率问题
- 为什么线程安全?
- 应为在方法上加上了内置锁, 并且方法是static的, 所以是类锁, 保证了所有的线程访问这个方法都必须排队, 所以保证了线程安全
- 有什么效率问题?
- 是应为所有的线程排队这个问题, 应为大量线程获取的时候,其实第一个线程就创建好了,其他的线程其实是不需要排队的, 所以存在效率问题
- 总结
- 不要用, 不为啥
懒汉式(线程安全, 同步代码块)
步骤
- 私有化构造
- 声明静态成员
- 对外提供公共静态方法
- 在方法内部创建对象
- 添加同步代码块
代码
package com.dance.design.designmodel.simpleinterestmodel; public class SimpleFive { } /** * 懒汉式(线程安全, 同步代码块) */ class SingleFive { // 创建静态常量 private static SingleFive singleFive; // 私有化构造 private SingleFive() { } /** * 提供一个公共静态方法 * * @return 类 */ public static SingleFive getInstance() { if (null == singleFive) { // 其实还是类锁 synchronized (SingleFive.class) { singleFive = new SingleFive(); } } return singleFive; } }
总结
- 我都不是很想总结这个单同步代码块的单利模式了, 在写的时候思考到了一些问题, 和大家聊聊问题吧
- 大家看代码,这个同步代码块,其实还是类锁, 这个同步块如果加在if块的内部, 还是会导致线程不安全说的那个问题,就是多线程卡在if块里面, 如果是加载if块外面, 就会和同步方法一样,直接卡到开头, 所我感觉我起的这个名字很好"单利Five", 怪不得排在第五
- 不要用,不为啥
双重检查
步骤
参考同步方法的步骤
将同步块放在if块的外面,然后在同步块的外面再包一层if块
代码
package com.dance.design.designmodel.simpleinterestmodel; public class SimpleSix { } /** * 双重检查 */ class SingleSix { // 创建静态常量 private static volatile SingleSix singleSix; // 私有化构造 private SingleSix() { } /** * 提供一个公共静态方法 * * @return 类 */ public static SingleSix getInstance() { // 再包一层if块用于卡线程, 如果有了就可以直接获取, 解决排队问题 if (null == singleSix) { // 其实还是类锁 synchronized (SingleSix.class) { if (null == singleSix){ singleSix = new SingleSix(); } } } return singleSix; } }
总结
- 优点
- 懒加载, 线程安全, 效率高
- 缺点
- emmm... 不知道怎么写了.
- 为什么线程安全?
- 应为采用了类锁, 线程去排队, 并且成员变量采用了volatile修饰
- 为什么需要volatile修饰?
- 因为这种双重检测机制在JDK1.5之前是有问题的,问题还是出在(//创建实例),由所谓的无序写入造成的。一般来讲,当初始化一个对象的时候,会经历
- 内存分配 -> 初始化 -> 返回对象引用
- 这种方式产生的对象是一个完整的对象,可以正常使用。但是JAVA的无序写入可能会造成顺序的颠倒,即
- 内存分配 -> 返回对象引用 -> 初始化
- 这种情况下对应到(//创建实例)就是singleton已经不是null,而是指向了堆上的一个对象,但是该对象却还没有完成初始化动作。当后续的线程发现singleton不是null而直接使用的时候,就会出现意料之外的问题。
- 解决方案:
- JDK1.5之后,可以使用volatile关键字修饰变量来解决无序写入产生的问题,因为volatile关键字的一个重要作用是禁止指令重排序,即保证不会出现内存分配、返回对象引用、初始化这样的顺序,从而使得双重检测真正发挥作用
- 为什么效率高?
- 应为在类锁的,外部和内部都有检查, 在创建一次之后,以后就不会走类锁了, 所以后续不会排队
- 总结
- 没错, 写不出来缺点, 就用这种吧
静态内部类
步骤
- 私有化构造
- 声明静态成员
- 声明静态内部类
- 内部类声明属性
- 提供对外公共静态方法
代码
package com.dance.design.designmodel.simpleinterestmodel; public class SimpleSeven { } /** * 双重检查 */ class SingleSeven { // 私有化构造 private SingleSeven() { } private static final class SingleSevenHolder { // 创建静态常量 static final SingleSeven singleSeven = new SingleSeven(); } /** * 提供一个公共静态方法 * * @return 类 */ public static SingleSeven getInstance() { // 再包一层if块用于卡线程, 如果有了就可以直接获取, 解决排队问题 // 其实还是类锁 return SingleSevenHolder.singleSeven; } }
总结
- 优点
- 线程安全, 懒加载, 效率高
- 缺点
- emm.. 一样不知道
- 为什么懒加载, 不是static的吗?
- 应为类只有在第一次调用或者其他类依赖的时候才会进行类加载, 类加载, 这个内部类没有没其他类依赖, 并且是内部的所以在加载外部类的时候,也不会加载内部类, 只有第一次调用 getInstance方法时才会触发类加载
- 为什么是线程安全的?
- 类加载只会触发一次, 除非类卸载,
- 为什么效率高?
- 只触发一次类加载, 不需要判断(双重检查的判断都省略了), 直接可以返回, 不用排队,
- 总结
- 用就完了,不为啥
枚举
步骤
- 创建属性
- 创建方法
代码
package com.dance.design.designmodel.simpleinterestmodel; public class SimpleEight { public static void main(String[] args) { SingleEight.INSTANCE.say(); } } /** * 静态内部类 */ enum SingleEight { INSTANCE; public void say(){ System.out.println("say 单利模式"); } }
总结
- 优点
- 线程安全, 开发简单, 防止反序列化创建, 效率高
- 缺点
- 一样不清楚
- 总结
- 用就完了, 大佬推荐[Effective Java作者 Josh Bloch 提倡]
源码剖析
JDK源码中的单利模式
- JDK中 java.long.Runtime就是经典的单利模式(饿汉式)
代码
package java.lang; import java.io.*; import java.util.StringTokenizer; import sun.reflect.CallerSensitive; import sun.reflect.Reflection; /** * 第一句话就表明了这是一个单利的类 * Every Java application has a single instance of class * ....... */ public class Runtime { // 声明私有的成员变量 一上来就new 所以是饿汉式(静态变量) private static Runtime currentRuntime = new Runtime(); // 提供对外的公共静态方法 public static Runtime getRuntime() { return currentRuntime; } // 私有化构造方法 /** Don't let anyone else instantiate this class */ private Runtime() {} }
单利模式注意事项和细节说明
- 单利保证了系统中只存在一个对象, 节省了系统资源, 对于一些需要频繁创建销毁的对象, 使用单利可以提高系统性能
- 当你想要获得一个单利类的时候,应该是调用公共静态方法获取,而不是通过new
- 使用场景
- 需要频繁的进行创建和销毁的对象
- 创建对象耗时过多或耗费资源过多(即: 重量级对象), 但又经常用的对象
- 工具类
- 频繁访问数据库或文件的对象
- 数据源
- Session工厂
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· winform 绘制太阳,地球,月球 运作规律
· AI与.NET技术实操系列(五):向量存储与相似性搜索在 .NET 中的实现
· 超详细:普通电脑也行Windows部署deepseek R1训练数据并当服务器共享给他人
· 上周热点回顾(3.3-3.9)
· AI 智能体引爆开源社区「GitHub 热点速览」