<JavaEE> 经典设计模式之 -- 单例模式(“饿汉模式”和“懒汉模式”实现单例模式)

news2024/12/23 2:38:21

目录

一、单例模式概述

二、“饿汉模式”实现单例模式

三、“懒汉模式”实现单例模式

3.1 单线程下的“懒汉模式”

3.2 多线程下的“懒汉模式”


一、单例模式概述

1)什么是单例模式?

单例模式是一种设计模式。

单例模式可以保证某个类在程序中只存在唯一实例,即不允许创建多份实例。

使用单例模式,上述要求就得到了检查和校验。

2)单例模式的实现形式

单例模式可以通过很多种方法实现,“饿汉模式”和“懒汉模式”是其中最基础的两种,本文只介绍这两种实现。

二、“饿汉模式”实现单例模式

通过代码演示“饿汉模式”实现的单例模式:

class Singleton{
    //新建一个唯一实例;
    private static Singleton instance = new Singleton();

    //方法返回唯一实例;
    public static Singleton getInstance() {
        return instance;
    }

    //将构造方法私有化;
    private Singleton() { }
}
1)上述代码做了什么?

创建了一个被 static 修饰的实例,这个实例成为了类属性。类对象只会有一个,这个类属性也只会有一个。

私有化构造方法,外部无法 new 新的实例,只能通过 get 方法获取唯一的那一个 instance。
2)为什么叫做“饿汉模式”?

上述代码中,实例是类属性。类属性在类加载的时候就创建了,创建时机早,十分“迫切”,因此称为“饿汉模式”。

代码证明“饿汉模式”返回的实例是唯一的:

public class Singleton_Demo0 {
    public static void main(String[] args) {
        //想直接new对象,就会报错;
        //Singleton instance = new Singleton();
        
        //两次调用getInstance()方法并分别赋值;
        Singleton instance1 = Singleton.getInstance();
        Singleton instance2 = Singleton.getInstance();

        //对比两个变量,发现是同一实例;
        if(instance1 == instance2){
            System.out.println("两个对象是同一个对象");
        }
    }
}

//运行结果:
两个对象是同一个实例
3)“饿汉模式”的单例模式在多线程下是线程安全的吗?

上述代码中,get 方法返回的是已经创建好的实例,这个操作本质上只是一个“读操作”,多个线程读取同一个变量并不会造成线程不安全。

因此“饿汉模式”的单例模式在多线程下是线程安全的。

三、“懒汉模式”实现单例模式

3.1 单线程下的“懒汉模式”

通过代码演示懒汉模式现的单例模式:

class Singleton{
    //声明一个变量作为类属性;
    private static Singleton instance = null;

    //判断变量是否为null,是则创建实例后返回,否则返回;
    public static Singleton getInstance() {
        if(instance == null){
            instance = new Singleton();
        }
        return instance;
    }

    //将构造方法私有化;
    private Singleton() { }
}
1)上述代码做了什么?

声明了一个类属性。类对象只会有一个,这个类属性也只会有一个。

私有化构造方法,外部无法 new 新的实例,只能通过 get 方法获取唯一的那一个 instance。
get 方法中根据变量是否为 null 判断是否应该创建实例。
2)为什么叫做“懒汉模式”?

上述代码中,实例是在程序员第一次调用 get 方法后才创建的,创建时机较晚,或者根本不用创建,因此称为“懒汉模式”。

3.2 多线程下的“懒汉模式”

1)单线程下的“懒汉模式”在多线程下是线程安全的吗?

答案是否定的,单线程下的“懒汉模式”在多线程下是线程不安全的,我们可以从以下两个方面分析:

“原子性”:

上述代码中判断变量是否为空的代码 —— if(instance == null),和实例化代码 —— 

instance = new Singleton(),并非是“原子”的。在多线程环境下,这就可能导致线程不安全。

可以使用 synchronized 关键字,将这两句代码加锁,解决这个问题。

内存可见性和指令重排序:

因为 instance 是一个被 static 修饰的共享数据,而且编译器内部可能对实例化的代码 —— new Singleton(),进行了编译器优化。

这就无法保证内存的可见性和指令的顺序执行,因此在多线程环境下可能导致线程不安全。

可以使用 volatile 关键字,对共享数据 instance 进行修饰,解决这个问题。

使用以上两个关键字的原因和方式,详细请参考以下博客:

阅读指针 -> 《synchronized 关键字 和 volatile 关键字》<JavaEE> synchronized关键字和锁机制 -- 锁的特点、锁的使用、锁竞争和死锁、死锁的解决方法-CSDN博客文章浏览阅读70次。介绍了 synchronized 关键字 和 锁机制,其中重点介绍了锁的特点、使用方法和死锁的相关内容。https://blog.csdn.net/zzy734437202/article/details/134742168<JavaEE> volatile关键字 -- 保证内存可见性、禁止指令重排序-CSDN博客文章浏览阅读59次。简单介绍什么是内存可见性和指令重排序。volatile关键字可以将这两种编译器优化强制关闭。https://blog.csdn.net/zzy734437202/article/details/134757070

2)“懒汉模式”在多线程下应该怎么编写?

根据上述分析,根据单线程模式下的“懒汉模式”进行改进。

方法如下:

增加 volatile 关键字对共享数据进行修饰。

为判断是否为 null 和 实例化的代码加锁,使这两句代码称为“原子”。

增加 volatile 关键字对共享数据进行修饰:

private volatile static Singleton instance = null;

为判断是否为 null 和 实例化的代码加锁,使这两句代码称为“原子”:

    public static Singleton getInstance() {
        synchronized (locker){
            if(instance == null){
                instance = new Singleton();
            }
        }
        return instance;
    }
3)“双重校验锁”

我们再仔细分析一下上述的 get 方法。

假设程序需要多次调用这个 get 方法,那么每一次进入都会进行加锁,加锁是会增加系统开销的。

那么是否真的有必要每次都加锁呢?

当 get 方法被第一次调用,实例就会被创建,那么后续再调用这个 get 方法时,返回实例就好了,加锁部分的代码块,完全可以不用执行。

在加锁的代码块之外,再增加一个if(instance == null)进行判断,那么实例在被创建之后,也就不会再进入加锁的代码块中了。

我们成功利用“双重校验锁”,优化了程序。

代码演示“双重校验锁”优化后的 get 方法:

    public static Singleton getInstance() {
        //这个if用于判断是否需要加锁;
        if(instance == null){
            synchronized (locker){
                //这个if用于判断是否需要新建实例;
                if(instance == null){
                    instance = new Singleton();
                }
            }
        }
        return instance;
    }

经过以上的完善和优化,我们终于可以写出在多线程下保证线程安全的“懒汉模式”单例模式了:

class Singleton{
    //声明一个变量作为类属性;
    private volatile static Singleton instance = null;

    private static final Object locker = new Object();

    //判断变量是否为null,是则创建实例后返回,否则返回;
    public static Singleton getInstance() {
        //这个if用于判断是否需要加锁;
        if(instance == null){
            synchronized (locker){
                //这个if用于判断是否需要新建实例;
                if(instance == null){
                    instance = new Singleton();
                }
            }
        }
        return instance;
    }

    //将构造方法私有化;
    private Singleton() { }
}

阅读指针 -> 《经典设计模式之 -- 使用阻塞队列实现“生产者-消费者模型”》

<JavaEE> 经典设计模式之 -- 使用阻塞队列实现“生产者-消费者模型”-CSDN博客自己实现了的阻塞队列,介绍了经典的设计模式“生产者-消费者模型”。https://blog.csdn.net/zzy734437202/article/details/134807241

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

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

相关文章

.9.png的创建

1、创建.9.png 选中图片,右击,选择Create 9-Patch file,点击确定会生成一个xxx.9.png的图片 2、绘制拉伸区域 在图片的最外边界绘制拉伸区域,按住鼠标左键不放,绘制完成后保存就可以使用了。绘制结果示意如下&…

Numpy数组中数据的排序【sort(),argsort()与 lexsort()】 (第13讲)

Numpy数组中数据的排序【sort(),argsort()与 lexsort()】 (第13讲)         🍹博主 侯小啾 感谢您的支持与信赖。☀️ 🌹꧔ꦿ🌹꧔ꦿ🌹꧔ꦿ🌹꧔ꦿ🌹꧔ꦿ🌹꧔ꦿ🌹꧔ꦿ🌹꧔ꦿ🌹꧔ꦿ🌹꧔ꦿ🌹꧔ꦿ🌹꧔ꦿ🌹꧔ꦿ🌹꧔ꦿ🌹꧔ꦿ🌹꧔ꦿ🌹꧔…

2.Feign使用、上下文隔离及源码阅读

目录 概述使用配置pom.xmlfeign 接口编写controller 测试降级处理pom.xmlapplication.yml代码 Feign如何初始化及调用源码阅读初始化调用 feign的上下文隔离机制源码 结束 概述 阅读此文,可以知晓 feign 使用、上下文隔离及源码阅读。源码涉及两方面:fe…

在做题中学习(31):电话号码的字母组合(全排列)

17. 电话号码的字母组合 - 力扣(LeetCode) 思路:既然要排列组合,就得先根据数字字符取出来 所以先定义一个string类的数组通过下标取到每个数字对应的映射。 string _numsTostr[10]{"","","abc"…

2023.12.3 关于 Spring Boot 拦截器 和 过滤器

目录 引言 Spring 拦截器实现 实例理解 Spring 过滤器实现 实例理解 拦截器和过滤器的区别 出身不同 触发时机不同 底层实现不同 支持的项目类型不同 使用场景不同 引言 原生 Spring AOP 实现统一拦截有两个难点难点一:定义拦截规则表达式 难点二&#…

用提问的方式来学习:冯·诺伊曼体系结构与操作系统OS

学习冯诺伊曼体系结构之前,我们要本着两个问题来学习: 什么是冯诺伊曼体系结构?为什么要有冯诺伊曼体系结构? 一、冯诺伊曼体系结构 1. 什么是冯诺伊曼体系结构? 那我们就先来回答一下什么是冯诺伊曼体系结构&#x…

VMALL 商城系统

SpringBoot MySQL Vue等技术实现 技术栈 核心框架:SpringBoot 持久层框架:MyBatis 模板框架:Vue 数据库:MySQL 阿里云短信,对象存储OSS 项目包含源码和数据库文件。 效果图如下:

奥特曼被指爱权力胜过金钱;人类才是「幻觉问题」根本原因丨 RTE 开发者日报 Vol.103

开发者朋友们大家好: 这里是 「RTE 开发者日报」 ,每天和大家一起看新闻、聊八卦。我们的社区编辑团队会整理分享 RTE (Real Time Engagement) 领域内「有话题的 新闻 」、「有态度的 观点 」、「有意思的 数据 」、「有…

【网络编程】-- 05 UDP进阶之在线聊天

网络编程 6 UDP 6.2.2 UDP聊天实现 “循环实现消息的发送和接收” 接收端只能接收到消息而无法反馈交流 接收: package com.duo.chat;import java.net.DatagramPacket; import java.net.DatagramSocket;public class UdpReceiveDemo1 {public static void mai…

12月11日作业

完善对话框,点击登录对话框,如果账号和密码匹配,则弹出信息对话框,给出提示登录成功,提供一个Ok按钮,用户点击Ok后,关闭登录界面,跳转到其他界面 如果账号和密码不匹配,弹…

【flink番外篇】1、flink的23种常用算子介绍及详细示例(完整版)

Flink 系列文章 一、Flink 专栏 Flink 专栏系统介绍某一知识点,并辅以具体的示例进行说明。 1、Flink 部署系列 本部分介绍Flink的部署、配置相关基础内容。 2、Flink基础系列 本部分介绍Flink 的基础部分,比如术语、架构、编程模型、编程指南、基本的…

宝塔PostgreSQL设置数据库远程访问

宝塔PostgreSQL设置数据库远程访问 宝塔的PostgreSQL1. 添加数据库2. 打开PostgreSQL设置界面3. 修改配置4. 重载配置/重启数据库 Docker的PostgreSQL1. postgresql.conf2. pg_hba.conf3. 重启数据库 注意其他问题 宝塔PostgreSQL设置数据库远程访问?docker容器Post…

这样的Python自动化测试面试题,测开来了都不一定都会把!

十、接口自动化 10.1 接口自动化怎么测试 ( Python requestspytest 版本) 原来我们接口自动化是用 python request pytest 执行 接口自动化其实主要就是接口测试的基础上填加了断言,参数化,动态关联 做接口自动化之前,我们也会划分模块&#…

Java毕业设计 SSM SpringBoot 在线学习系统

Java毕业设计 SSM SpringBoot 在线学习系统 SSM SpringBoot 在线学习系统 功能介绍 首页 图片轮播 视频推荐 在线学习 学习介绍 评论 收藏 资料中心 资料详情 下载资料 话题讨论 文档发布 试题中心 系统公告 登录 注册学生 个人中心 试题记录 错题本 我的收藏 算法演示 结果分…

智能优化算法应用:基于蝴蝶算法3D无线传感器网络(WSN)覆盖优化 - 附代码

智能优化算法应用:基于蝴蝶算法3D无线传感器网络(WSN)覆盖优化 - 附代码 文章目录 智能优化算法应用:基于蝴蝶算法3D无线传感器网络(WSN)覆盖优化 - 附代码1.无线传感网络节点模型2.覆盖数学模型及分析3.蝴蝶算法4.实验参数设定5.算法结果6.参考文献7.MA…

课后作业7.3.1:构造一个自己的小操作系统

构造一个自己的 mini 操作系统 任务描述 请实现如下功能: 1.写一个命令解释器程序 mysh.c ,其功能是接收用户输入的命令并给出反馈。要求该程序既支持内部命令 cd、sync、exit ;也支持外部命令,即可以接收 cat、ls 等命令&#x…

【电路笔记】-电位器

电位器 文章目录 电位器1、概述2、电位器类型2.1 旋转电位器2.2 滑块电位器2.3 预设和微调电位器2.4 变阻器 3、电位器示例14、电位器作为分压器5、电位器示例26、变阻器6、滑块变阻器7、线性或对数电位器8、总结 当连接的轴物理旋转时,电位计和变阻器的电阻值会发生…

【linux】yum安装时: Couldn‘t resolve host name for XXXXX

yum 安装 sysstat 报错了: Kylin Linux Advanced Server 10 - Os 0.0 B/s | 0 B 00:00 Errors during downloading metadata for repository ks10-adv-os:- Curl error (6): Couldnt resolve host nam…

Aesthetically Relevant Image Captioning 美学上相关的图像字幕

1.摘要 图像美学质量评估(AQA)旨在为图像指定数字美学等级,而图像美学字幕(IAC)旨在生成图像美学方面的文本描述。在本文中,我们研究了图像AQA和IAC,并提出了一种新的IAC方法,称为审美相关图像字幕(ARIC)。基于大多数图像的文本评…

基于ssm高校教师科研信息展示网站论文

摘 要 互联网发展至今,无论是其理论还是技术都已经成熟,而且它广泛参与在社会中的方方面面。它让信息都可以通过网络传播,搭配信息管理工具可以很好地为人们提供服务。针对高校教师科研项目展示信息管理混乱,出错率高&#xff0c…