当前位置: 首页 > news >正文

多线程-案例-单例模式

文章目录

  • 前言
  • 1 饿汉模式
  • 2 懒汉模式
    • 简介
    • 关于synchronized
    • 双重检查
    • volatile
  • 总结

前言

我们通过这篇文章来讨论多线程中的单例模式

  1. 饿汉模式
  2. 懒汉模式

单例模式也可以理解为单独的一个实例。
也就是在一个类中只new出一个对象。
这通常是因为在多线程中如果不加以限制,
会创造出很多实例对象,又因为创建销毁是需要耗费资源的。
这样频繁且大量的创建会导致浪费掉了机器资源。

有两个经典的模式需要我们来学习一下,分别是懒汉与饿汉。

1 饿汉模式

饿汉也就是字面意思。
你很饿,拿到了一个面包立刻就吃进去了。
对应到程序设计中,就是不管三七二十一,类一加载就直接创建实例。
我们看看代码:

classHungryMan{privatestaticfinalHungryManMAN=newHungryMan();//其余字段...privateHungryMan(){// 逻辑...}publicstaticHungryMangetInstance(){returnMAN;}publicvoidsout(){System.out.println("我十分甚至九分的饥饿");}// 其余逻辑...}

我们来掰扯掰扯这个代码。
首先可以看到,这个构造方法是private所修饰的,
也就是说,我们根本无法在类外部new出一个HungryMan对象。
这里也就避免了创建出多个实例的可能。
这时候你要问了,那这个类如何使用呢?
好,代码中这一段很重要:private static final HungryMan MAN = new HungryMan();
首先这个MAN是由private、final修饰的,代表了MAN不可更改。
其次MAN的类型是一个HungryMan,这说明它可以调用该类的实例方法。
最后是new HungryMan();,虽然我们无法在外界使用构造方法,但是你看我们在类的里面使用了构造方法。这样我们就创建出了一个实例。
如何使用??
我们通过HungryMan.getInstance();这个方法就可以拿到MAN了。
既然我们已经拿到了对象,那么就可以直接调用这个类里面由public修饰的方法。
比如:
HungryMan.getInstance().sout();
//输出:我十分甚至九分的饥饿
由于是静态的,当类加载到JVM里面时这个实例就创建出来了。
我们还得想一个问题——在多线程模式下,这种写法是否会引发线程安全问题?
其实不会。
类的静态变量只会在类加载时初始化一次。由于后续线程对该实例只有读取操作,不会引发线程安全问题。
看看代码:

publicclassTest{publicstaticvoidmain(String[]args){for(inti=0;i<100;i++){newThread(()->{System.out.println(HungryMan.getInstance());}).start();}}}

这是输出结果:

可以看见输出的对象地址都是一样的,也就证明了多线程环境下获取到的是同一个实例。

2 懒汉模式

简介

懒汉就是字面意思,很懒。
什么事情都要拖到最后才去做。
在计算机中懒并非是一个贬义词,反而是一个褒义词。

我们想想,饿汉模式是立刻创建好对象,
但是在很多场景,我们并不是很需要立即就把对象创建出来。

大家玩我的世界这款游戏的时候,我们都知道世界并不是一下子就全部创建好的,那样太费资源了。而是角色走到哪里,世界就创建到哪里,处于一个动态的过程。

我们在手机上看小说,其文章也不是一次性就在后台里全部加载好,而是看到哪里加载到哪里。

上述的加载方式也叫做懒加载。
非必要不加载,这样就节省了许多的资源。

我们看看代码是如何实现的

classSingleton2{privatestaticSingleton2instance;privateSingleton2(){}//私有化构造方法publicstaticSingleton2getInstance(){if(instance==null){//静态方法这里只能用类名synchronized(Singleton2.class){if(instance==null){instance=newSingleton2();}returninstance;}}returninstance;}publicvoidout(){System.out.println("这是一个单例模式的懒汉式的案例");}

我们看到啊private static Singleton2 instance;我们并不是立即给Singleton2 new一个对象的。而是先让他保持一个null的状态。

当我们在外界调用getInstance这个方法的时候才会去创建一个实例对象。

关于synchronized

我们来细致的看看这个getInstance这个方法。

如果不在意外界条件只考虑单线程的情况下我们这样写其实就满足懒汉模式了

if(instance==null){instance=newSingleton2();}returninstance;

当instance为空的时候我们创建一个,
当instance不为空的时候我们直接返回现成的即可。

那我们为啥要加上锁呢?

其实啊这就是因为抢占式运行,

举个例子。
这里有两个线程,线程A,线程B。
现在CPU运行到程序A这里,
程序A执行if(instance == null)此时条件为true。
程序A准备执行下一步,此时CPU去运行线程B了,
线程B同样执行if(instance == null)也为true。

线程B将instance创建出一个实例对象,并且将instance返回。
此时CPU回过头来,
继续执行instance = new Singleton2();,
看!这就创建出了多个实例,这和我们所期待单例模式不符合。

为了应对上述情况,我们就加上一把锁。防止程序之间的插队。

我们还要再想想,为什么锁的外面还有一个if??

因为这个锁在保护的同时也将程序的执行效率大大降低了。
我们为了提高效率从而在锁的外层加上if判断。

双重检查

如果不要外层if,
以后不论instance是否创建好了,
每个线程都要排队等锁释放才能继续往下运行,
这样就变成了串行,跟排队上厕所一样,

大家可以这样理解,
我们假设需要去停车场停车,
此时停车场门口有一个显示器,上面写了空余的停车位的个数,
现在车很多,
大家都排着队,前车走后车进,
此时若是停车位已经满了,
后面的车子并不知道这个消息,因为只有在停车场门口才能看到车位剩余,
后面的车好不容易走到停车位的门口了,
却看到的是车位已满,
这就很难受了,
此时我们开发一个小程序,
车主能在手机上看到车位是否有剩余,
这样,
如果显示满了,
那么其余车主就不会一直排队等候了,
早就去找其他车位了,

volatile

我们想想,为什么还要加上一个 volatile??

如果没有 volatile,CPU 可能会为了优化效率把步骤 2 和 3 调换顺序。这就可能导致:线程 A 刚执行完第 3 步(还没初始化对象),线程 B 就在最外层的 if(instance == null) 判断时发现不为空,直接拿走了一个还没初始化完成的“半成品”去使用,结果导致程序崩溃。

加上 volatile 就像给停车场的管理员发了指令:必须等车停稳了(对象初始化完),才能把车位状态改成“已占用”


总结

  1. 单例模式的核心:通过私有化构造方法确保外部无法随意创建实例,并提供一个静态方法作为全局访问点。

  2. 饿汉模式:类加载时即创建,线程安全,但可能造成资源浪费(如果一直没被使用)。

  3. 懒汉模式:首次使用时才创建(懒加载),节省资源,但需要处理多线程下的同步问题。

  4. 双重检查锁定(DCL):懒汉模式中,外层的 if 是为了提高性能(避免实例创建后仍频繁加锁),内层的 if 是为了保证单例(防止多次创建)。

  5. volatile 的必要性:在懒汉模式中,必须使用 volatile 修饰 instance 变量,以防止指令重排序导致的线程安全问题。

http://www.jsqmd.com/news/659256/

相关文章:

  • 35 openclawCQRS模式应用:分离读写操作提升性能
  • 别再只跑Demo了!用MaixPy IDE给你的K210人脸识别项目加个‘本地数据库’(附完整代码)
  • 【优化求解】基于粒子群算法面向弹性提升的多种应急资源参与配电网抢修恢复附Matlab代码
  • Phi-3-mini-4k-instruct与LSTM模型结合:时序预测优化
  • 基于认知负荷理论的职场新人算法学习策略:如何循序渐进,避免挫败感。
  • 智能代码生成性能调优实战手册(企业级低延迟落地白皮书)
  • 【LangGraph】03-LangGraph之State
  • STM32H750项目实战:如何把DMA数据精准丢进512KB高速SRAM(Keil MDK配置详解)
  • Agent 的生命周期管理与治理
  • 嵌入式系统中文支持实战——从Ubuntu到Buildroot的locale配置与疑难解析
  • Java Stream sorted()排序实战:从基础到高级Comparator应用
  • 一句话自动剪Vlog!连BGM都能丝滑卡点,CutClaw有点太会了
  • 从MNIST代码里学到的:PyTorch模型调试与可视化实战技巧(附常见错误排查)
  • 神经符号AI融合:下一代开发范式
  • LSTM时序预测与Pixel Script Temple结合:生成动态像素动画序列
  • CodeBlocks-20.03 新手上路:从零配置到首个C++程序
  • 2026风机箱哪家好?新风换气机源头厂家怎么选?优质风机箱实力推荐:江苏亿恒空调 - 栗子测评
  • SpringBoot项目集成AspectJ:从依赖配置到实战问题排查
  • 从理论到实践:伺服三环控制的参数整定与Simulink仿真指南
  • NaViL-9B实战教程:使用NaViL-9B构建自动化图文审核与合规检查系统
  • B站视频转文字终极方案:Bili2text如何革命性提升你的学习与创作效率?
  • 告别重复造轮子:用若依的表单构建器,5分钟搞定复杂业务表单(附动态菜单配置)
  • 具身智能表征的ImageNet来了!机器人终于看懂了人类世界
  • Python实战:立体像对空间前方交会算法解析与实现
  • ccmusic-database行业落地:在线教育平台音乐鉴赏课自动流派标注系统
  • 2026专业空压机厂家推荐:蚌埠正德,深耕行业多年,满足各类工况使用需求 - 栗子测评
  • 机械臂抓取实战:如何用YOLOv5和GraspNet实现动态目标精准抓取(附完整代码)
  • 别再只盯着成本中心了!用SAP EC-PCA做利润中心分析,从配置到报表的全流程解读
  • 2026文化石市场亮点:技术精湛的厂家推荐,文化石/天然石/砌墙石/贴墙石/石材/冰裂纹/碎拼石,文化石厂商哪家好 - 品牌推荐师
  • 单片机实战解析:从时序到代码,手把手实现DS18B20温度采集