单例模式 - 饿汉式与懒汉式详解

2023-11-04

什么是单例模式?

对于一个软件系统中的某些类而言,只有一个实例很重要,就像Windows中的任务管理器一样,只能打开一个。如果不适用机制对窗口对象进行唯一化,必定会弹出多个窗口。如果这些窗口显示的内容完全一致,则是重复对象,浪费内存资源。如果内容不一致,则意味着某一瞬间系统有多个状态,与实际不符,也会给用户带来误解,不知道哪一个是真实的状态。因此有时确保系统某个对象的唯一性非常重要。

单例对象的类必须保证只有一个实例存在,许多时候整个系统只需要拥有一个的全局对象,这样有利于我们协调系统整体的行为。

Spring框架中就用到了单例模式,比如说配置的一个Bean,如果不做配置,则默认就是单例的。

 

优点:

1、可以确保所有的对象都访问同一个实例

2、可以节约系统资源,避免了频繁创建和销毁的对象

3、避免对共享资源的多重占用

缺点:

1、不适合变化的对象,如果同一类型的对象总是要在不同场景发生变化,单例就会造成数据错误

2、职责过重,一定程度上违背了”单一职责原则“

3、如果实例化的对象长时间不被利用,系统可能会认为是垃圾而被挥手

 

 

单例模式分为两种,一种是饿汉式,一种是懒汉式

一、饿汉式单例类

public class HungrySingleton {
	// 类加载,初始化
	private static final HungrySingleton instance = new HungrySingleton();
	
	private HungrySingleton() {}
	
	public static HungrySingleton getInstance() {
		return instance;
	}
}

饿汉式的特点就是当类被加载是,静态变量instance就会被初始化,此时类的私有构造方法会被调用,单例类的唯一实例将被创建。

二、懒汉式单例类

public class LazySingleton {
	private static LazySingleton instance = null;
	
	private LazySingleton() {}
	
	public static LazySingleton getInstance() {
		// 如果实例没被创建,就创建
		if(instance == null) {
			instance = new LazySingleton();
		}
		return instance;
	}
}

这是极简版的懒汉式单例类,它的构造方法同样是私有的。但是,它与饿汉式不同的是,它在第一次被引用的时候才会实例化,即在类加载的时候不会将自己实例化。它在第一次调用getInstance方法的时候实例化,这种技术又称为延迟加载技术,即在需要的时候才加载实例。这段代码存在线程问题,下面开始解决这个问题,优化代码。

为了避免多个线程同时调用getInstance方法,可以使用关键字synchronized包裹。

// 确保任意时刻只有一个线程可以执行该方法
synchronized public static LazySingleton getInstance() {
	// 如果实例没被创建,就创建
	if(instance == null) {
		instance = new LazySingleton();
	}
	return instance;
}

上诉代码虽然加了synchronized锁定线程,但是每次调用getInstance都需要进行线程锁定判断,在多线程高并发访问环境下会导致系统性能大大降低。分析发现,核心代码是instance = new LazySingleton(),所以我们仅需锁定这段代码即可。

public static LazySingleton getInstance() {
	// 如果实例没被创建,就创建
	if(instance == null) {
		synchronized(LazySingleton.class) {
			instance = new LazySingleton();
		}
	}
	return instance;
}

这段代码看似解决了性能问题,实际上,还存在问题,可能存在单例对象不唯一的情况。比如说,在某一时刻,线程A和线程B同时调用getInstance方法。因为加锁的原因,有一个线程(A)先执行代码,另一个(B)需要等待。因为锁定的是实例化的那块代码,没有锁定If(instance == null)这块代码,也就是说两个线程都通过了这个判断语句。那么线程A执行完,创建了一个实例,该线程B去执行了,那么B又会创建一个实例。这两个实例肯定不是同一个,他们都是单独new出来的。

因此,需要加一个双重判断检查锁定来解决这个问题。

public class LazySingleton {
	// 被volatile修饰的变量可以确保多个线程能正常处理
	private volatile static LazySingleton instance = null;
	
	private LazySingleton() {}
	
	public static LazySingleton getInstance() {
		// 第一层判断,如果实例已经创建,跳过
		if(instance == null) {
			synchronized(LazySingleton.class) {
				// 第二层判断,如果实例创建,跳过
				if(instance == null) {
					instance = new LazySingleton();
				}
			}
		}
		return instance;
	}
}

通过两层判断,解决了实例对象可能不唯一的问题。这里可能有人会疑惑,第一层可以不要。实际这里也是一个性能问题。第一层我们要解决的是实例是否已经创建的问题,创建了,就可以不用去判断线程锁定问题。第二层触发条件就是第一层没通过,即没有创建实例,是为了解决多个线程同一时刻调用创建对象的方法时可能造成对象不唯一的问题的。

如果使用双重检查锁定来实现懒汉式,需要在静态成员instance前加上volatile修饰,该修饰符在JDK1.5以上才能用,它会屏蔽Java虚拟机所做的一些代码优化,可能会导致系统运行效率降低,所以这种办法似乎也不是最完美的。

3.饿汉式和懒汉式对比

饿汉式的优点是无需考虑多个线程同时访问的问题,可以确保实例的唯一性。从调用速度和反应时间来说,因为它一开始就创建了对象,它是优于懒汉式的。但是从资源利用角度来说,它不如懒汉式,因为饿汉式无论你是否能用到这个单例对象,它都会给你创建,懒汉式是在你用到才会给你创建,而且系统加载时就需要创建饿汉式单例对象,可能会造成加载时间变长。

懒汉式,需要去解决多线程的问题,特别是当单例类作为资源控制器,在实例化时必然涉及到资源初始化的问题,可能会耗费大量时间,意味着多线程首次引用此类几率变大,所以要通过双重判断来进行控制,就会导致性能下降。

4.完美方案 - 使用静态内部类实现单例模式

在Java中,可以通过Initialization on Demand Holder(IoDH)来实现单例模式,该方法可以实现延迟加载,又可以保证线程安全,不影响系统性能,但是很多语言是不支持IoDH的。

public class Singleton {
	private Singleton() {}
	
	// 静态内部类
	private static class HolderClass {
		private final static Singleton instance = new Singleton();
	}
	
	public static Singleton getInstance() {
		return HolderClass.instance;
	}
}
public class Test {
	public static void main(String[] args) {
		Singleton s1 = Singleton.getInstance();
		Singleton s2 = Singleton.getInstance();
		System.out.println(s1 == s2);
	}
}

 结果为true,表明通过这种方式创建的对象是唯一的。

因为静态单例对象没有作为SIngleton的成员变量,所以在类加载的时候不会实例化Singleton,第一次调用getInstance时就会加载内部类HolderClass,内部类中定义了一个静态变量instance,这个时候才会初始化这个成员变量,由Java虚拟机来保证线程安全,确保该成员变量只能初始化一次。这里因为没有用synchronized锁定,所以不会造成任何性能影响。

 

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

单例模式 - 饿汉式与懒汉式详解 的相关文章

  • 设计模式-单一职责原则介绍与理解

    描述 一个类应该专注于实现一个功能 好处 便于代码复用 举例 俄罗斯方块游戏 首先可以想到的是游戏逻辑与界面的分离 也就是说逻辑一个类 界面部分一个类 这样做的好处就是我们可以复用游戏逻辑的代码 例如我们用java写了一个基于PC端的俄罗斯
  • 行为型模式-策略模式

    package per mjn pattern strategy 抽象策略类 public interface Strategy void show package per mjn pattern strategy 具体策略类 用来封装算法
  • 离散仿真引擎基础作业与练习

    作业内容 一 简答题 1 解释 GameObjects 和 Assets 的区别与联系 2 下载几个游戏案例 分别总结资源 对象组织的结构 3 使用 debug 验证 MonoBehaviour 基本行为或事件触发条件 4 了解 GameO
  • 设计模式学习笔记(一)之单例模式

    单例模式 作用 保证一个类只有一个实例 并且提供访问这个实例的全局访问点 应用场景有 数据库连接池 spring中 Bean默认是单例 Servlet中 每一个Servlet是单例 配置文件的类 一般是单例 优点 单例只生成一个实例 减少系
  • Java设计模式-装饰者模式Decorator

    介绍 装饰者模式的核心思想是通过创建一个装饰对象 即装饰者 动态扩展目标对象的功能 并且不会改变目标对象的结构 提供了一种比继承更灵活的替代方案 需要注意的是 装饰对象要与目标对象实现相同的接口 或继承相同的抽象类 另外装饰对象需要持有目标
  • 六大设计原则--开闭原则

    定义 software entities like classes modules and functions should be open for extension but closed for modifications 一个软件实体
  • [C++]备忘录模式

    备忘录模式 Memento Pattern 保存一个对象的某个状态 以便在适当的时候恢复对象 备忘录模式属于行为型模式 github源码路径 https github com dangwei 90 Design Mode 此文件包含 mai
  • 设计模式-建造者模式

    文章目录 建造者模式 创建复杂对象的优雅方式 什么是建造者模式 建造者模式的使用场景 优缺点 示例 使用建造者模式创建电脑对象 建造者模式 创建复杂对象的优雅方式 在软件开发中 有时候我们需要创建具有复杂结构和多个组件的对象 直接在客户端代
  • 设计模式(十)装饰器模式

    装饰器模式是一种非常有用的结构型模式 它允许我们在不改变类的结果的情况下 为类添加新的功能 我们来举例说明一下 首先添加一组形状 它们都实现了形状接口 public interface Shape String getShape class
  • linux内核中的设计模式

    创建型 Object Pool Object Pool模式可以提升性能 尤其是在对象的分配 初始化成本高 使用频率高 但使用时间短的情况下 对象池可以设置对象池的大小和回收时间缓存预分配的对象 NT和Linux都有简单的预分配缓存对象的机制
  • [C++]外观模式

    外观模式 Facade Pattern 隐藏系统的复杂性 并向客户端提供了一个客户端可以访问系统的接口 这种类型的设计模式属于结构型模式 它向现有的系统添加一个接口 来隐藏系统的复杂性 这种模式涉及到一个单一的类 该类提供了客户端请求的简化
  • Java设计模式之装饰者设计模式Decorator Pattern

    目录 一 基本概念 二 结构 1 图示 三 案例演示 被装饰对象的基类 一个接口 有cost 和description 两个抽象方法 具体被装饰的对象 实现上面这个接口 装饰者抽象类 基类 实现drink接口 具体的装饰者类 糖 具体装饰者
  • 程序员必知的23种设计模式之组合模式

    文章目录 1 模式引出 学校院系展示需求 1 1 传统方案 1 2 传统方案问题分析 2 组合模式基本介绍 2 1 方案修改 3 组合模式解决的问题 4 组合模式的注意事项和细节 1 模式引出 学校院系展示需求 编写程序展示一个学校院系结构
  • 设计模式-享元模式

    一 概念 如果在一个系统中存在多个相同的对象 那么只需要共享一份对象的拷贝 而不必为每一次使用都创建新的对象 目的是提高系统性能 上面的概念乍一听好像单例模式其实不是 单例模式只保存一个对象 但是这里可以有很多个不同对象 但是每个对象只有一
  • JavaScript设计模式-02-单例模式

    Javascript 设计模式 02 单例模式 简介 单例就是保证一个类只有一个实例 实现的方法一般是先判断实例是否存在 如果存在直接返回 如果不存在就创建了再返回 确保了一个类只有一个实例对象 在JavaScript里 单例作为一个命名空
  • 设计模式(不懂)

    面试中经常问到设计模式 我才对这个东西了解了一下 才发现他是没有开发的新大陆 是oo设计的更高级别 能把设计模式搞懂 那oo你就搞的差不多了 随便看了还是很有意思的 虽然不怎么懂 百科名片 相关书籍 设计模式 Design pattern
  • Java设计模式:模板方法模式

    作者主页 欢迎来到我的技术博客 个人介绍 大家好 本人热衷于 Java后端开发 欢迎来交流学习哦 如果文章对您有帮助 记得 关注 点赞 收藏 评论 您的支持将是我创作的动力 让我们一起加油进步吧 文章目录 一 模板方法模式的定义 二 模板方
  • 设计模式(3)--对象结构(5)--外观

    1 意图 为子系统中的一组接口提供一个一致的界面 Facade模式定义了一个高层接口 这个接口使得 这一子系统更加容易使用 2 两种角色 子系统 Subsystem 外观 Facade 3 优点 3 1 对客户屏蔽了子系统组件 减少了客户处
  • 设计模式(三)-结构型模式(4)-组合模式

    一 为何需要组合模式 Composite 在代码设计中 有种情况是对象之间存在层次关系 即对象之间会存在父结点和子结点的关系 比如在文件管理系统中 所有文件和文件夹形成树状结构 文件夹目录里存在子文件夹和文件 文件夹属于枝结点 文件属于叶结
  • 系列一、 单例设计模式

    一 单例设计模式 1 1 概述 单例模式 Singleton Pattern 是Java中最简单的设计模式之一 这种类型的设计模式属于创建者模式 它提供了一种创建对象的最佳方式 这种模式涉及到一个单一的类 该类负责创建自己的对象 同时确保只

随机推荐

  • MC9S12XEP100的ATD模块(ADC12B16CV1)

    网上的各种示例基本都是用同步 轮询的方式来使用ATD模块的 自己封装ATD模块时想利用中断改成异步的方式 结果出现了莫名其妙的问题 我明明没有开启比较中断的 结果还是跳到了比较中断里头去了 一气之下 把整个文档翻译了一遍 顺带给大家分享了
  • 什么是mAP(mean average Precision)

    Mean Average Precision 即 平均AP值 AP Average precision 单类标签平均 各个召回率中最大精确率的平均数 的精确率 AP PR Precision Recall 曲线下面积 mAP Mean Av
  • Sharding-JDBC 自定义一致性哈希算法 + 虚拟节点 实现数据库分片策略

    1 Sharding JDBC 分片策略 分片操作是分片键 分片算法 也就是分片策略 目前Sharding JDBC 支持多种分片策略 标准分片策略 对应StandardShardingStrategy 提供对SQL语句中的 IN和BETW
  • YOLOv7默默更新了Anchor-Free

    作者 小书童 编辑 集智书童 点击下方卡片 关注 自动驾驶之心 公众号 ADAS巨卷干货 即可获取 点击进入 自动驾驶之心 目标检测 技术交流群 首先恭喜YOLOv7登录CVPR2023的顶会列车 YOLOv7 u6分支的实现是基于Yolo
  • python画饼图加牵引线_python 用 matplotlib 绘制圆环形嵌套饼图步骤详解

    原博文 2020 05 09 22 23 1 加载库 import matplotlib as mpl import matplotlib pyplot as plt 2 单层圆环饼图 配置字体 显示中文 mpl rcParams font
  • js常规的循环方法

    JavaScript 的常规循环方法有以下几种 1 for 循环 最常用的一种循环方法 可以指定循环的起始值 结束值和步长 for let i 0 i lt array length i 循环体 2 while 循环 只要条件为真 就会一直
  • C++中如何使函数返回数组

    C 中如何使函数返回数组 以前使用java返回数组这些类型都比较方便 用c 的时候突然发现c 不支持返回数组 我就找了下应该怎么实现这这种返回 在C 中 数组不是一种类型 因此不能被直接返回 一般有两种方法来返回一个数组 返回一个指向数组的
  • 终于Github App支持中文简体了!

    GitHub 于 2008 年 4 月 10 日正式上线 目前 其注册用户已经超过 350 万 托管版本数量也是非常之多 2018 年 6 月 4 日 微软宣布 通过 75 亿美元的股票交易收购代码托管平台 GitHub Github作为互
  • 2022年03月青少年软件编程(C语言)等级考试试卷(一级) 试题解析

    1 本题20分 双精度浮点数的输入输出 输入一个双精度浮点数 保留8位小数 输出这个浮点数 时间限制 1000 内存限制 65536 输入 只有一行 一个双精度浮点数 输出
  • 编码的几种实现

    几个概念 Unicode是一种 编码 所谓编码就是一个编号 数字 到字符的一种映射关系 就仅仅是一种一对一的映射而已 Unicode只是一个符号集 它只规定了符号的二进制代码 却没有规定这个二进制代码应该如何存储 GBK UTF 8是一种
  • HTML注释

    目录 HTML 注释标签 实例 实例 实例 条件注释 软件程序标签 一个完整的实例 注释标签 用于在 HTML 插入注释 HTML 注释标签 您能够通过如下语法向 HTML 源代码添加注释 实例 注释 在开始标签中有一个惊叹号 但是结束标签
  • LeetCode:1625. 执行操作后字典序最小的字符串

    题目链接 1625 执行操作后字典序最小的字符串 力扣 LeetCode 题目信息 给你一个字符串 s 以及两个整数 a 和 b 其中 字符串 s 的长度为偶数 且仅由数字 0 到 9 组成 你可以在 s 上按任意顺序多次执行下面两个操作之
  • 用大数乘法计算阶乘

    在比较小的范围内阶乘可以递归实现 而求更大的数的阶乘一般用到long long长整形数 不过 即使这样 在耗时和再大些的阶乘上力有不逮 所以 在输入比较大的情况下 用大数乘法计算阶乘是最好的选择 计算过程分2步 1 输入字符串s 将它的值保
  • 瑞吉外卖【用户移动端】

    用户移动端 一 手机验证码登录 1 短信发送 1 1 短信服务介绍 1 2 阿里云短信服务 2 手机验证码登录 2 1 需求分析 2 2 数据模型 2 3 代码开发 二 菜品展示 购物车 下单 1 用户地址薄 1 1 需求分析 1 2 数据
  • VLT:Vision-Language Transformer用于引用的视觉语言转换和查询生成分割

    摘要 在这项工作中 我们解决了引用分割的挑战性任务 引用分割中的查询表达式通常通过描述目标对象与其他对象的关系来表示目标对象 因此 为了在图像中的所有实例中找到目标实例 模型必须对整个图像有一个整体的理解 为了实现这一点 我们将引用分割重新
  • kali Linux的优点与缺点

    Kali Linux简介 用于数字取证操作系统 Kali Linux是基于Debian的Linux发行版 设计用于数字取证操作系统 由Offensive Security Ltd维护和资助 最先由Offensive Security的Mat
  • 使用X-WIN32 EXCEED等软件显示远程LINUX桌面的设置

    href http blog bcchinese net shiaohuazhang Services Pingback aspx rel pingback gt 使用X WIN32 EXCEED等软件显示远程LINUX桌面的设置 RED
  • 使用思维导图,优雅的完成自己的代码

    我自己常常在写代码的时候 会突然搞不清变量用来干嘛的 也会被理不清的逻辑搞得自己异常烦躁 我甚至常常暗示自己我不适合写代码 思维总是那么不清晰 直到我发现了思维导图的妙用 最开始使用思维导图的时候 我其实是用来记知识点的 然而某一刻就灵光一
  • Scrum是用来发现问题的

    原文链接作者 Mark Levison 机械的Scrum对比真正的Scrum 差别在哪里 最近 我和一个朋友聊到了他们公司实施Scrum的情况 他们有些迷茫 在实施Scrum之前 他们经常为了访问一台测试机而不得不等上一个小时 甚至更多时间
  • 单例模式 - 饿汉式与懒汉式详解

    什么是单例模式 对于一个软件系统中的某些类而言 只有一个实例很重要 就像Windows中的任务管理器一样 只能打开一个 如果不适用机制对窗口对象进行唯一化 必定会弹出多个窗口 如果这些窗口显示的内容完全一致 则是重复对象 浪费内存资源 如果