如何快速手撕单例类?一文教会你

news2024/11/25 4:19:34

👨‍🎓作者:bug菌
✏️博客:CSDN、掘金、infoQ、51CTO等
🎉简介:CSDN|阿里云|华为云|51CTO等社区博客专家,历届博客之星Top30,掘金年度人气作者Top40,51CTO年度博主Top12,掘金 | InfoQ | 51CTO等社区优质创作者,全网粉丝合计15w+  ;硬核微信公众号「猿圈奇妙屋」,欢迎你的加入!免费白嫖最新BAT互联网公司面试题、4000G pdf电子书籍、简历模板等海量资料。

...

✍️温馨提醒:本文字数:1999字, 阅读完需:约 5 分钟

🏆本文收录于《Java进阶实战》,专门攻坚指数提升。

本专栏致力打造最硬核 Spring Boot 从零基础到进阶系列学习内容,🚀均为全网独家首发,打造精品专栏,专栏持续更新中…欢迎大家订阅持续学习。

小伙伴们在批阅文章的过程中如果觉得文章对您有一丝丝帮助,还请别吝啬您手里的赞呀,大胆的把文章点亮👍吧,您的点赞三连(收藏⭐+关注👨‍🎓+留言📃)就是对bug菌我创作道路上最好的鼓励与支持😘。时光不弃🏃🏻‍♀️,创作不停💕,加油☘️

1. 前言

        哈喽,小伙伴们,你们好呀,今天我们就不整那枯燥无味的知识点了,偶尔换换口味,我们来玩点高级的;由于很多小伙伴都在给我传递一种负面情绪,今年的工作很难找,我就在想是不是八股文没准备充足啊?于是我就在总结高频笔试题,借此想把整理到的笔试题进行集合式的讨论,不仅帮助大家理解,也能帮助自己加深理解,何乐而不为呢。

2. 环境说明

本地的开发环境:

  • 开发工具:IDEA 2021.3
  • JDK版本: JDK 1.8
  • Spring Boot版本:2.3.1 RELEASE
  • Maven版本:3.8.2

3. 需求分析

        手写一个单例?那么首先你就要清楚单例是什么?如果都不清楚何为单例,那无法下手!首先我就带着大家简单回顾一下设计模式之单例模式的相关知识点吧。

3.1 概念

        单例模式是java中设计模式里最简单的模式之一,属于创建型模式。这种模式它提供了一种创建对象的最佳方式,在创建对象的过程中,只涉及一个类,且该类负责创建自身对象,并保证只能创建单个对象实例。

        总而言之,该模式的主要目的就是确保一个类只能有一个实例存在。

3.2 特点

单例模式特点可总结为以下3点

1、单例类只有一个实例对象。

2、该实例对象必须由单例类本身自行创建。

3、单例类必须提供一个共有的静态方法向外面提供这个实例。

3.3 分类

        单例模式可根据实例创建的时机进行分类,可分为[饿汉式、懒汉式]两类。

饿汉式:类加载就会创建单实例对象。

懒汉式:类加载不会导致该单实例对象被创建,只有在被使用的时候才会创建。

        总而言之,分类的意义在于实际运用场景决定,各有各的优缺点。比如懒汉模式,只有在被使用的时候才创建,节省资源,体现了延迟加载的思想;但在并发场景下同一时间被多个线程调用,则很有可能被创建出多个实例,违背唯一实例原则。而恶汉模式,写法上简单,而且在多线程下也能保证唯一实例,但如果外部一直未调用该实例又先实例化了,这部分资源也就白白浪费了。

4. 代码演示

        如下我将分别从单例模式的两种分类来实现代码单例,仅供参考:

4.1 懒汉式单例(线程安全)

代码实现如下:

public class LazySingLeton {

    //私有构造方法
    private LazySingLeton() {
        System.out.println("生成LazySingLeton实例一次");
    }

    //在成员位置创建该类的对象
    private static LazySingLeton instance = null;

    public synchronized static LazySingLeton getInstance() {
        if (instance == null) {
            instance = new LazySingLeton();
        }
        return instance;
    }
}

        注意:此种写法虽然解决了线程安全问题,但synchronized同步的方法是静态的,会导致进入该方法时JVM会锁定LazySingLeton这个类,锁的粒度太大,大量线程同时访问的时候直接阻塞,性能太低。

        接下来,我们还是先写个main函数顺序调用上三遍懒汉式单例测试一波看看。

//测试
public static void main(String[] args) {
    LazySingLeton.getInstance();
    LazySingLeton.getInstance();
    LazySingLeton.getInstance();
}

        大家可以看到,实例确实只被创建了一次!而非三次。

4.2 恶汉式单例

代码实现如下:

public class NoLazySingLeton {

    // 私有构造方法
    private NoLazySingLeton() {
        System.out.println("生成NoLazySingLeton实例一次!");
    }

    // 在成员位置创建该类的对象
    private static NoLazySingLeton instance = new NoLazySingLeton();

    // 对外提供静态方法获取该实例对象
    public static NoLazySingLeton getInstance() {
        return instance;
    }
} 

写个main函数顺序调用上三遍恶汉式单例,静待结果:

//测试
public static void main(String[] args) {
    //调用三遍
    NoLazySingLeton.getInstance();
    NoLazySingLeton.getInstance();
    NoLazySingLeton.getInstance();
}

大家可以看到,实例确实只被创建了一次!而非三次。

        对比以上两种写法,其实就是在初始化instance的时候才会出现线程安全问题,一旦初始化完成线程不安全就完全不存在了,这也就是懒汉恶汉式的抉择问题了,重点也就在于你是何应用场景了。

4.3 懒汉式单例(双重检查模式)

        最后,我们再来讨论一下,对比上方保证线程安全懒汉式而言,若想做到即解决性能问题又能保证线程安全,那可以这么干,浅浅听我分析,对于 getInstance() 方法来说,绝大部分的操作都是读 操作,读操作是线程安全的,所以没必让每个线程必须持有锁才能调用该方法,我们只需要调整加锁的时机,除了初始化的时候会出现加锁的情况,后续的所有调用都会避免加锁而直接返回,从而达到解决性能消耗的问题。具体代码大家请看如下,我把代码注释都写的明明白白。

public class DoubleLazySingLeton {
    
    private static DoubleLazySingLeton instance;
    
    // 私有构造方法
    private DoubleLazySingLeton() {
        System.out.println("生成DoubleLazySingLeton实例一次!");
    }

    // 对外提供静态方法获取该对象
    public static DoubleLazySingLeton getInstance() {
        // 第一次判断,如果instance不为null,不进入抢锁阶段,直接返回实例.
        if (instance == null) {
            //instance未实例化的时候才加锁
            synchronized (DoubleLazySingLeton.class) {
                // 抢到锁之后再次判断是否为null
                if (instance == null) {
                    instance = new DoubleLazySingLeton();
                }
            }
        }
        return instance;
    }
}

        但是话又说回来, 虽然【双重检查模式】能保证性能及线程安全,但是也并非完美无缺,在多线程情况下,可能会有空指针异常的问题,因为JVM在实例化对象的时候会进行优化和指令重排序操作。

        若要解决双重检查模式带来空指针异常的问题,你只需要使用[volatile]关键字,因为volatile关键字可以保证可见性和有序性(禁止指令重排序),顾保证了new DoubleLazySingLeton()创建对象实例化过程的顺序性,具体请看如下:

4.4 懒汉式单例(DCL双重校验锁)

具体代码如下:

public class DoubleLazySingLeton {
    
    //关键字volatile保证对象实例化过程的顺序性。
    private static volatile DoubleLazySingLeton instance;

    // 私有构造方法
    private DoubleLazySingLeton() {
        System.out.println("生成DoubleLazySingLeton实例一次!");
    }

    // 对外提供静态方法获取该对象
    public static DoubleLazySingLeton getInstance() {
        // 第一次判断,如果instance不为null,不进入抢锁阶段,直接返回实例.
        if (instance == null) {
            //instance未实例化的时候才加锁
            synchronized (DoubleLazySingLeton.class) {
                // 抢到锁之后再次判断是否为null
                if (instance == null) {
                    instance = new DoubleLazySingLeton();
                }
            }
        }
        return instance;
    }
}

由于volatile关键字它可以禁止指令重排序来保证一定的有序性,自然就解决了空指针异常的问题。


5. 热文推荐🔥

  1. 浅谈你对单例类中使用volatile关键字的理解
  2. Mysql分页 vs Oracle分页 对比分析
  3. Java 如何实现手动连接数据库(MySQL或Oracle)?
  4. Java 如何实现获取客户端公网IP地址?
  5. 为什么print和println输出java对象时会打印内存地址?
  6. 如何解决springboot拦截器@Autowried注入为空问题?
  7. MyBatis中的discriminator鉴别器如何使用?
  8. 如何快速手撕单例类?一文教会你
  9. 如何保证三个线程按顺序执行?不懂我教你
  10. 简谈你对synchronized关键字的使用

6. 最后🔥

🏆本文收录于《Java进阶实战》,需要的小伙伴可以进去瞅瞅。

本专栏致力打造最硬核Java进阶系列学习内容,🚀均为全网独家首发,打造精品专栏,专栏持续更新中…欢迎大家订阅持续学习。

       我是bug菌,一名CSDN / 阿里云 / 华为云 / 51CTO 等社区博客专家,历届博客之星Top30,掘金年度人气作者Top40,51CTO年度博主Top12,掘金/InfoQ/51CTO等社区优质创作者,全网粉丝合计10w+,硬核微信公众号「猿圈奇妙屋」,欢迎你的加入!一起学习,一起变强。

关注公众号,获取最新BAT互联网公司面试题、4000G pdf电子书籍、简历模板等硬核资源

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

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

相关文章

数据结构与算法基础-学习-24-遍历之DFS(深度优先搜索)和BFS(广度优先搜索)

目录 一、遍历定义 二、遍历实质 三、DFS 四、BFS 五、宏定义 六、自定义类型 七、函数实现 1、DFS(邻接矩阵实现) 2、DFS(邻接表实现) 3、BFS(邻接矩阵实现) 4、BFS(邻接表实现&…

在idea中创建maven

说明:maven是一款管理和构建java项目的工具,使用maven,可规范开发,提高开发效率;maven的安装参考:http://t.csdn.cn/623Ah 配置Maven环境 创建maven,先要做准备工作,把idea中的环境…

带你开发一个远程控制项目---->STM32+标准库+阿里云平台+传感器模块+远程显示-------之 MQTT连接阿里云平台

第一篇: (13条消息) 带你开发一个远程控制项目---->STM32标准库阿里云平台传感器模块远程显示。_海口飞鹏岛科技有限公司的博客-CSDN博客 第二篇: (13条消息) 带你开发一个远程控制项目---->STM32标准库阿里云平台传感器模块远程…

C语言---数组

1、一维数组的创建和初始化 1.1、数组的创建 数组是一组相类型元素的集合。 数组的创建方式: type_t arr_name [const_n];//type_t 是指数组的元素类型 //const_n 是一个常量表达式,用来指定数组的大小。1.2、数组的初始化 数组的初始化是指&#x…

国产替代10BASE-T ST7010QNL 应用局域网的以太网变压器/扼流器

Hqst华强盛导读: 华强盛是电子产品国产替代大军中的一员,随着中国电子产业的快速发展,越来越多的电子产品开始出现了国产替代品。这些国产替代品在性能、品质和价格等方面都有了显著的提升,成为了工厂用户的首选。 国产替代10BAS…

DERT(DEtection TRansformer) ONNX直接推理!!

目录 1.前言 2. ONNX模型 (1) backbone使用的是resnet50 (2) Transformer结构 (3)模型输出 3.代码展示(不收费!!!) 4.结果展示 5.源代码地址 1.前言 DETR DETR的全称是DEtection TRansformer,是Facebook提出的基于…

c++实现【典型的旅行商问题(TSP)】实现配送中心最多可以用2辆车对8个客户进行运输配送

假定配送中心最多可以用2辆车对8个客户进行运输配送。每个车辆载重均 为8吨,车辆每次配送的最大行驶距离为50km,配送中心(编号0)与8个客 户之间及8个客户相互之间的距离d; (i, j= 1, 2, ... 8)、8个客户的货物需 求r;(j= 1, 2... 8)如表1所示。要求寻找一条路径, 使得配送总…

Codeforces Div.2 1798B Three Sevens题解

题目: 传送门 B. Three Sevens time limit per test 2 seconds memory limit per test 256 megabytes input standard input output standard output Lottery "Three Sevens" was held for m days. On day i, ni people with the numbers ai,1…

生态系统NPP及碳源、碳汇模拟——土地利用变化、未来气候变化、空间动态模拟

由于全球变暖、大气中温室气体浓度逐年增加等问题的出现,“双碳”行动特别是碳中和已经在世界范围形成广泛影响。碳中和可以从碳排放(碳源)和碳固定(碳汇)这两个侧面来理解。陆地生态系统在全球碳循环过程中有着重要作…

手动计算校正年龄、性别后的标准化死亡率 (SMR)

分析队列人群有无死亡人数超额,通常应用标准人群死亡率来校正,即刻观察到中的实际死亡数(D)与定一个标准的死亡人数(E),D与E之比称为死亡比(standarized Mortality ratio&#xff0c…

运筹学-单纯形法

一、单纯形法的求解思路 单纯形法求解线性规划的思路:在高斯消去法的基础上,发展为求解变量数大于方程数,并且使目标函数值优化的方法。从线性方程中找到一个个的单纯形,每个单纯形(图形的顶点)可以得到一组…

支付宝 小程序 抽奖组件 大转盘

介绍 使用支付宝原有的大转盘营销组件进行改造的,由于背景使用的图片,目前只支持 6 个奖品,一般情况下的大转盘都是这个规格。 转盘停止:之前使用的是计算角度来完成的,没有那种缓慢停止的动画。现在加了一个缓慢停止…

Android实现皮肤主题修改

最近在做App内皮肤切换功能,想了很久方案,写了个皮肤更换工具类,适配N皮肤种类。 话不多说,直接捋一下我的设计思路,因为我的App默认为黑色主题,因此在做其他皮肤主题时,我的图片命名方式是直接…

Fastjson核心解析器DefaultJSONParser,解析算法递归下降算法,实例解析json的步骤

先恭喜热火没有在3-0的情况下被凯尔特人翻盘,抢七获胜成功晋级总决赛~ 最近的项目用到了fastjson,因为源码比较容易搞到,所以就拿来简单的了解了一下,json的主要功能就是解析json和生成json字符串,今天主要是从解析jso…

基于vue3.0简单的页面使用

基于vue3.0简单的页面使用 项目效果图项目文件图package.jsonmain.jsApp.vueviews/Tutorial.vueviews/TS.vueviews/Docs.vueviews/Community.vueviews/Blog.vueviews/About.vueutils/create.jsxutils/defineCom.jsutils/DragIcon.jsutils/someName.tsutils/TS.tsstores/client.…

win11任务栏时间改成12时制

需求:默认24小时值,想改成12小时3:49 方法:设置-时间和语言-语言和区域-管理语言设置-格式 将时间格式改成带tt的

2022年长三角高校数学建模竞赛C题隧道的升级改造与设计解题全过程文档及程序

2022年长三角高校数学建模竞赛 C题 隧道的升级改造与设计 原题再现: 某地现存一旧式双洞隧道,现计划将该隧道在旧貌基础上升级改造。在升级改造前,需进行定标与设计。考虑到该隧道洞壁附着特殊涂料,无人机在洞内通信信号较差&am…

网络面试题:什么是 TCP/IP?

目录标题 什么是 TCP/IP?1) 网络接口层:2) 网络层:3) 传输层:4) 应用层: 2.数据包3.网络接口层4.网络层1) IP:2)地址解析协议 ARP3)子网 5 传输层1)UDP:2)TCP: 6 应用层运行在TCP协议上的协议:运行在UDP协议上的协议&…

Netty 实现百万级连接服务的难点和优点分析总结

推送服务 还记得一年半前,做的一个项目需要用到 Android 推送服务。和 iOS 不同,Android 生态中没有统一的推送服务。Google 虽然有 Google Cloud Messaging ,但是连国外都没统一,更别说国内了,直接被墙。 所以之前在…

Lua学习笔记:C/C++和Lua的相互调用

前言 本篇在讲什么 C/C和Lua的相互调用 本篇适合什么 适合初学Lua的小白 适合需要C/C和lua结合开发的人 本篇需要什么 对Lua语法有简单认知 对C/C语法有简单认知 依赖Lua5.1的环境 依赖VS 2017编辑器 本篇的特色 具有全流程的图文教学 重实践,轻理论&…