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

多线程系列五:面试中常考的单例模式

1.设计模式

在了解单例模式之前我们先要了解设计模式:
设计模式是一种软性规定,遵守了设计模式,代码的下限就被兜住了,类似于棋谱,是大佬设计出来的,让小白程序员也能写出好的代码
设计模式有很多种,不同语言中也有不同的设计模式,设计模式可认为是对编程语言语法的补充

2.单例模式

单例=单个实例(对象),某个类在一个进程中,只应创建出一个实例,原则上不应该有多个,使用单例模式,就可以对代码进行一个更严格的校验和检查,有的代码中,需要使用一个对象来管理/持有大量的数据,此时有一个对象即可,比如一个对象管理10G数据,如果不小心创建了多个对象,内存空间就会成倍增长,机器就顶不住了


唯一对象如何保证呢?
1️⃣:可通过君子约定,写一个文档约定每个接手维护代码的程序员都不能把这个类创建多个实例,但是并不靠谱
2️⃣:希望让机器对代码中指定的类,创建的实例个数进行校验,如果发现创建多个实例了,就直接对编译报错,如果能做到这一点,就可以放心的写代码

**Java语法本身没有直接约定某个对象创建几个实例,就要用一些奇淫巧技来实现,单例模式具体的实现方式有很多,最常见的就是“饿汉模式”和“懒汉模式”两种

2.1饿汉模式

我们先看一下整体代码,接下来我们一一解释

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

这个引用就是希望创建的唯一实例的引用
在这里插入图片描述
instance是Singleton类对象里持有的属性,在Java中每个类的类对象只存在一个,类对象static属性自然也只有一个,因此instance指向的这个对象就是唯一的一个对象

在这里插入图片描述
其他代码没法new,从根本上让其他人想new也new不了,只能用getInstance

后续其它代码若想使用这个类的实例,就要通过getInstance()这个方法来进行获取,不应该在其它代码中重新new这个对象,而是使用这个方法获取到现成的对象

在main中的使用
在这里插入图片描述

对于饿汉模式,getInstance直接返回Instance实例,这个操作本质上是读操作,多个线程读取同一个变量,是线程安全的

2.2懒汉模式单线程版

在懒汉模式中,类加载的时候不创建实例,第一次使用的时候才创建实例,在懒汉模式中,代码有读也有写,是线程不安全的

我们先来看一下单线程版的代码,如果首次调用getInstance,那么此时Instance引用为null,就会进入if条件,从而把实例创建出来,如果后续再使用getInstance,由于Instance已经不再是null了,此时就不会进入if,直接返回之前创建好的引用了,这样设定仍可保证该类的实例是唯一一个,与此同时,创建的实例的时机就不是程序启动时了,而是第一次调用getInstance时,这个操作执行时机就不知道了,就看你的实际需求,大概率要比饿汉模式这种方式晚一些,甚至有可能整个程序压根用不到这个方法,也就把创建的操作省下了

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

会造成创建出两个实例的问题
在这里插入图片描述
如果单个线程使用这个代码是安全的,但是在多线程情况下,由于线程是抢占式执行,不确定执行的实际,就会创建出多个实例

加上synchronized可以改善这里的线程问题

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

但是Instance已经创建过了,此时后续再调用getInstance就都是直接返回Instance实例了,此时操作就是纯粹的读操作了,也就不会有线程安全问题了,这时仍然是每次调用都先加锁再解锁,效率就非常低了
接下来我们看看如何改进

2.3懒汉模式多线程版(这是个重点的面试题,主要是考虑为什么要加东西,每个地方解决什么问题要讲清楚)

class SingletonLazy1{private static volatile SingletonLazy1 instance = null;private SingletonLazy1(){}public static SingletonLazy1 getInstance(){if (instance==null){synchronized (SingletonLazy1.class){if(instance==null){instance=new SingletonLazy1();}}}return instance;}
}

下面这个图片非常重要,解释了重要的逻辑
在这里插入图片描述

理解双重if判定/volatile:
加锁/解锁是一件开销比较高的事情,而懒汉模式的线程不安全只是发生在首次创建实例的时候,因此后续使用的时候就不必再进行加锁了
外层的if就是判定下看当前是否把Instance实例创建出来了
同时为了避免“内存可见性”导致读取的Instance出现偏差,于是补充上volatile
当多线程首次调用getInstance,大家可能都发现Instance为null,于是又继续往下执行来竞争锁,其中竞争成功的线程再完成创建实例的操作
当这个实例创建完了之后,其他竞争锁的线程就被里层if挡住了,也就不会继续创建其他实例

我们通过一个更形象的例子来理解这个问题
1️⃣:有三个线程,开始执行getInstance,通过外层的if(instance==null)知道了实例还没有创建的消息,于是开始竞争同一把锁
在这里插入图片描述
2️⃣:其中线程1率先获取到锁,此时线程1通过里层的if(instance==null)进一步确认实例是否已经创建,如果没有创建,就把这个实例创建出来
在这里插入图片描述
3️⃣:当线程1释放锁之后,线程2和线程3也拿到锁,也通过里层的if(instance==null)来确认实例是否已经创建,发现实例已经创建出来了,就不再创建了
4️⃣:后续的线程,不必加锁,直接就通过外层if(instance==null)就知道实例已经创建了,从而不再尝试获取锁了,降低了开销

相关文章:

  • SQL 与 Python:日期维度表创建的不同选择
  • LINUX——例行性工作
  • LeetCode 220 存在重复元素 III 题解
  • 高铁座位指示灯系统技术深度解析:从物联网到智慧出行的实践路径
  • [论文阅读]Deep Cross Network for Ad Click Predictions
  • 机器学习例题——预测facebook签到位置(K近邻算法)和葡萄酒质量预测(线性回归)
  • 多模态训练与微调
  • TypeScript简介
  • MPay码支付系统第四方聚合收款码多款支付插件个人免签支付源码TP8框架全开源
  • MD2card + Deepseek 王炸组合 一键制作小红书知识卡片
  • Qwen3与Deepseek R1对比(截止20250506)
  • CentOS 7 安装指定版本 Docker 及镜像加速/配置优化攻略
  • 计算机视觉与深度学习 | 基于数字图像处理的裂缝检测与识别系统(matlab代码)
  • SiC 材料及器件在高频大功率领域的应用现状
  • MCP服务发展现状的有趣发现
  • 【每天一个知识点】使用 apriori() 函数获取频繁项集
  • 论广告系统对存算分离架构的应用
  • 代码随想录算法训练营第60期第二十八天打卡
  • 1ms城市算网稳步启航,引领数字领域的“1小时经济圈”效应
  • C++复习
  • 王耀庆化身“罗朱”说书人,一人挑战15个角色
  • 世界人形机器人运动会将在北京“双奥场馆”举行
  • 暴雨蓝色预警:南方开启较强降雨过程
  • 重庆动物园大熊猫被游客扔玻璃瓶,相同地方曾被扔可乐瓶
  • 汪海涛评《线索与痕迹》丨就虚而近实
  • 体坛联播|国米淘汰巴萨晋级欧冠决赛,申花击败梅州避免连败