程序员棋谱之一——单例模式

单例模式呢是一种设计模式;什么是设计模式呢?这就相当于一个下棋中的一个族谱,我们学习设计模式可以提高我们写代码的下限,但如果想提高上限就得靠自己了。目前呢主流的设计模式有26种,我们今天聊聊单例模式。

单例模式的运用场景是只实例一个对象,而什么时候会涉及到只实例一个对象呢?当一个对象的数据过于庞大的时候我们就会只实例一个对象,如我们常用的搜索引擎,一个搜索引擎里边的数据是非常多的,实例一个对象消耗的内存是非常庞大的,所有我们在使用这个类时只能实例一个对象,不然可能导致服务器崩溃。

单例模式有两种设计方案,一个是饿汉方式一个是懒汉模式。

饿汉模式

class Singleton{ private static Singleton instance = new Singleton(); public static Singleton getInstance(){ return instance; } private Singleton() { System.out.println("构建成功!!!"); } }

这里讲饿汉模式之前我们先聊聊static修饰的成员变量,我们之前学过static了但过了许久可能忘了;static修饰的成员变量呢在代码运行是就会自动加载,也就是说呢即使我们不使用这个类我们,只要我们运行了代码,成员变量就会自动的加载到内存中,而这里的成员变量是直接new Singleton,所有她是在我们运行代码的瞬间就会直接创建对象,这也是为啥这个代码会被称为饿汉的意思,他在代码运行的瞬间就创建了,说明他已经非常饥饿了。

这个到吗中阻止这个类创建第二个对象的关键要点是构造方法使用了private修饰,使得她不可在其他类中创建。

懒汉模式

懒汉模式呢是我们这章节学习的重点,在计算机中我们并不想要饿汉模式那种刚运行就创建对象的方法,我们一般习惯于在我们需要的时候才创建对象,这是为什么呢?我们都知道程序运行时会在内存或CPU中消耗资源的,饿汉模式呢会从程序开始到结束都在内存中占用空间,这使得很多资源浪费掉,而懒汉模式呢只有在用到的时候才创建对象,这使得内存或CPU的资源能够高效的利用起来,

懒汉模式代码如下:

class Singleton{ private static Singleton instance = null; public static Singleton getInstance(){ if(instance == null){ instance = new Singleton(); } return instance; } private Singleton(){ } }

懒汉模式与饿汉模式的不同之处在于头把istance设计为了null这使得程序运行起来也不会占用资源,而后如果需要用到这个类时直接引用getInstance方法来使用,当第一次使用时会创建对象,第n次使用时会引用同一个对象而实现单例模式。

这是我们有一个问题,这两个模式线程安全吗?

线程安全

对于饿汉模式中当程序跑起来了只涉及到一次修改操作即开头的Singleton的对象创建,即使多个线程使用这个饿汉模式的类也不会影响到线程安全问题。

对于懒汉模式呢?他是涉及到线程安全问题的,我们知道线程安全问题主要考虑到的是这个线程的执行是否为原子性,是否需要修改,而懒汉模式中的getInstance()不仅不是原子的(三个指令,判断是否为空,新创对象或赋值,返回instance)而且还是可修改的(新创对象的引用),故而线程是不安全的。

我们来演示一遍:

在这里中我们设计了两个线程一个假设为t1,另一个假设为t2;当t1线程执行到判断instance是否为空的时候,由于线程的转换是分时复用的,可能t2线程也执行判断instance是否为空,这是两个线程都会进入if语句,然后t1线程执行了new操作并返回instance,此时线程t2也执行了以上操作,这是这个代码就会执行了两次创建对象,就不符合了单例模式。

那如何修改呢?

加锁是一个解决线程安全问题的常规手段,如何加锁呢?

class Singleton{ private static Singleton instance = null; private static Object object = new Object(); public static Singleton getInstance(){ synchronized (object){ if(instance == null){ instance = new Singleton(); } } return instance; } private Singleton(){ } }

我们上述推导发现这个线程安全无非是两个线程因为争判断instance是否为null而进入if语句中,我们可以直接把if语句给锁上即可完成线程安全问题。

可这是又有了新的问题,这个线程安全问题主要是两个线程第一次使用这个类引起的,如果后续我们在引用这个对象还得因为这个锁的问题造成堵塞等到浪费太多时间了,这对于程序来说是致命的,哪有解决方案吗?

线程优化

class Singleton{ private static Singleton instance = null; private static Object object = new Object(); public static Singleton getInstance(){ if(instance == null) { synchronized (object) { if (instance == null) { instance = new Singleton(); } } } return instance; } private Singleton(){ }

我们可以通过在加锁前在判断一次instance是否为空来解决因为锁的问题而造成的线程等待。

指令重排序

我们学到这里应该能清楚的知道jvm会帮我们优化代码,使得我们的代码逻辑不变的情况下加快运行速度,我们举个例子简单分析咋优化的:

当我妈叫我去买菜,他给我的菜单上是西红柿,鸡蛋,茄子,黄瓜;但按着这个顺序来买菜明显发现效率太低了,我就会耍点小心机:调整买菜的顺序,按照茄子,鸡蛋, 黄瓜,西红柿,的顺序来买,这样会加快了买菜的效率,但要买的菜都买回来了,而懒汉模式和这有什么关联呢?

我们知道新建对象会涉及到三个指令操作:1)申请内存空间,2)在空间上构造对象,3)在空间的首地址,赋值引用变量。正常来说一般程序运行都是按照这三步骤的顺序执行的,但Jvm可能会为了提高效率错开这几个顺序,可能按1 3 2来执行,当然在单线程模式可能并不影响逻辑,毕竟最终还是创建了一个新的对象,但如果多线程的话影响就大了!!!

我们还是假设有两个线程,t1线程和t2线程。

当我们先执行的是t1线程,执行到这里,JVM按照上述的1 3 2执行,此时会先把一个野指针(Java中没有指针,但明白意思即可)赋值给instance,此时instance就不为空了,又因为线程的执行是分时复用的,故而可能这是直接换成了t2来执行,这里instance已经不是空了就会造成t2线程直接跳过了锁,直接来到了return,这是t2线程拿到的就是一个野指针了!!!完全不符合预期结果,造成了线程安全问题!!!!

咋解决呢?我们之前学习过volatile,这个关键字能够解决成员变量中的内存可见性问题,在这里也非常适用,volatile同样也能解决指令重排序问题。使用懒汉模式的最终代码如下:

class Singleton{ private static volatile Singleton instance = null; private static Object object = new Object(); public static Singleton getInstance(){ if(instance == null) { synchronized (object) { if (instance == null) { instance = new Singleton(); } } } return instance; } private Singleton(){ } }

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/1201622.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

rpc节点: synchronized (this) + 双检锁,在 race condition 的情况下分析

结合rpc节点刷新业务,讲解 Java 中 synchronized (this) 的作用、原理和在代码里的具体意义。 这段代码的核心逻辑回顾 public SolanaRpcClient client() {SolanaRpcClient client this.healthyClient;// 定期检查健康状态if (client null || System.currentTim…

二进制不同位数【牛客tracker 每日一题】

二进制不同位数 时间限制:1秒 空间限制:256M 知识点:位运算 网页链接 牛客tracker 牛客tracker & 每日一题,完成每日打卡,即可获得牛币。获得相应数量的牛币,能在【牛币兑换中心】,换取…

MAC 怎样加密压缩 zip 包?

Mac上加密压缩ZIP包有三种主流方式:终端命令行(原生免费,无需额外软件)、第三方图形化工具(操作简单,支持更强加密)、以及Ventura及更新系统的Finder扩展(部分用户可用)。以下是详细步骤与对比。 一、终端命令行加密(推荐,原生无依赖) macOS自带zip命令支持加密,…

救命神器10个AI论文写作软件,助本科生轻松搞定毕业论文!

救命神器10个AI论文写作软件,助本科生轻松搞定毕业论文! 论文写作的救星,AI 工具如何改变你的学习方式 在当今这个信息爆炸的时代,本科生的学术写作任务变得越来越繁重。无论是课程论文还是毕业论文,都需要大量的时间和…

Pixels 医疗影像一站式解决方案从入门到精通

Pixels 医疗影像一站式解决方案从入门到精通 各位搞医疗影像的小伙伴些,你们有没有遇到过这种尴尬:DICOM 文件堆得到处都是,想找个病例翻半天,数据格式复杂得让人头大,更别说还要做 AI 分析了。莫慌,今天给…

Linux 内存管理中的 Overcommit(过度分配)机制及OOM Killer 的处理逻辑详解

您描述的是 Linux 内存管理中的 Overcommit(过度分配)机制 及其潜在风险,以及 OOM Killer 的处理逻辑。下面我将这一过程和相关概念系统地梳理一下。 1. 内存分配机制 Linux 内核中,应用程序通过 malloc() 等函数请求内存时&…

MySQL InnoDB Cluster升级到MySQL 8.4.x

MySQL InnoDB Cluster升级到MySQL 8.4.x2026-01-22 20:11 潇湘隐者 阅读(0) 评论(0) 收藏 举报上一篇文章总结了MySQL单实例从MySQL 8.0.35 升级到MySQL 8.4.x版本,这里简单总结一下MySQL InnoDB Cluster的升级到M…

LangGraph MCP Tool Calling Agent:让企业级智能体开发不再头大

LangGraph MCP Tool Calling Agent:让企业级智能体开发不再头大 用 LangGraph 开发复杂的 AI 智能体时,头疼的可能不是代码逻辑处理,而是如何让智能体安全、高效地访问企业内部的数据和工具。这个问题困扰了不少开发者,搞不好还要…

2026年电动刮研刀厂家推荐,提升生产效率与加工精度

在2026年的电动刮研刀市场中,了解正规的刮硏工厂、床身刮研制造商与直销厂家至关重要。这些厂商不仅在技术上不断进步,同时也把客户的需求放在首位。通过深入分析,建议关注它们的生产质量、技术水平和售后服务,以确…

做自媒体3年,终于找到稳定免费图床:CloudFlare-ImgBed实测

90%的博主都不知道!免费100G图床竟比付费的还稳定~ 写文章、做分享时,你是不是也总被图床问题折磨?免费的容量小得可怜,付费的一年要花几百块,更糟的是还常遇到文件失效、大文件传不了的坑,辛苦写的内容全白费!…

Mac Mouse Fix:让几十块的普通鼠标也能拥有丝滑触控板体验

Mac Mouse Fix:让几十块的普通鼠标也能拥有丝滑触控板体验 习惯了 Mac 触控板的丝滑手感,再切回普通鼠标总觉得不得劲儿,滚动生硬卡顿不说,那些好用的手势操作也全没了,搞得人好不安逸。 🔗 Claude Code 国…

数列分块入门学习笔记

分块九讲前言: 本篇主要是作者写完数列分块入门1~9后想写下一篇文章来记录自己的学习的历程,如果有错误的地方可以指出 分块的概念: 其实,分块是一种思想,而不是一种数据结构. 从 NOIP 到 NOI 到 IOI,各种难度的分…

FastScheduler:让 Python 定时任务变得优雅简单

FastScheduler:让 Python 定时任务变得优雅简单 写定时任务这事儿,估计每个 Python 开发者都遇到过。Python 自带的 schedule 库虽然简单,用着确实顺手,但一遇到异步任务、时区处理、失败重试这些高级场景,立马就感觉…

HanaVerse:把本地大模型变成二次元虚拟女友,这才是我们想要的 AI

HanaVerse:把本地大模型变成二次元虚拟女友,这才是我们想要的 AI 说实话,在本地跑大模型确实挺爽的,不用把数据上传到云端,隐私安全有保障,而且还能白嫖各种开源模型。但是吧,每天对着冷冰冰的…

2026年物业管理行业发展核心趋势解析:服务升级与价值重塑

物业管理作为城市治理与居民生活保障的核心环节,其服务质量不仅直接关系到业主的居住体验,更对房产保值增值具有重要影响。当前,国内物业管理行业正处于从传统基础服务向多元化、智能化、专业化转型的关键阶段,行业规模持续扩大&a…

从 0 到 1 认识大模型:核心原理与价值应用指南

在 AI 技术飞速迭代的今天,“大模型” 早已不是专业领域的小众术语,而是渗透到内容创作、智能交互、行业解决方案等多个场景的核心技术。但它究竟是什么?与普通 AI 模型有何本质区别?本文将用通俗的语言拆解大模型的核心概念、发展…

实用指南:spark的静态内存管理机制

pre { white-space: pre !important; word-wrap: normal !important; overflow-x: auto !important; display: block !important; font-family: "Consolas", "Monaco", "Courier New", …

Qt国际化实战指南:使用翻译官实现多语言应用

引言:为什么需要国际化?在当今全球化的软件开发环境中,应用程序往往需要面向不同国家和地区的用户。Qt作为一个跨平台的C框架,提供了完善的国际化(i18n)支持,允许开发者轻松实现多语言界面。通过…

智能体插件研发应该的技巧

pre { white-space: pre !important; word-wrap: normal !important; overflow-x: auto !important; display: block !important; font-family: "Consolas", "Monaco", "Courier New", …

Vue3登录注册验证码实战

以下是使用 Vue.js 实现登录、注册和验证码功能的完整代码示例。我将逐步解释关键部分,并提供可运行的 HTML 文件代码。代码使用了 Vue 3 的 Composition API,并模拟了验证码生成(真实应用中应通过后端 API 获取验证码)。 思路说…