【设计模式】单例模式

news2025/7/20 23:55:45

单例模式属于创建型模式,是最简单的一种设计模式。当一个类在程序中只需要创建唯一全局对象时(如网站计数类、日志管理类、线程池类……),就可以使用单例模式。单例模式规定一个类只能创建一个实例,之后不能再创建新的实例。所有线程无论何时获取的都是该类的唯一对象,这样做节省了创建一个新对象的资源开销。

文章目录

  • 单例模式的使用
    • 单例模式类图
    • 实现方法
      • 第一步,创建网站计数器类(单例类)
        • 网站计数器
      • 第二步,创建测试类测试
        • 测试类
        • 实现效果
    • 单例模式的几种实现方法
      • 线程安全的懒汉加载方法
        • 演示的单例类
      • 线程不安全的懒汉加载方法
        • 演示的单例类
      • 饿汉式加载方法
        • 演示的单例类
      • 双检锁加载方法
        • 演示的单例
      • 静态内部类登记方法
        • 演示的单例类
      • 枚举型加载方法
        • 演示的单例类
        • 测试类
        • 测试效果

单例模式的使用

​ 单例模式保证一个类只存在一个对象,并提供访问该对象的静态方法,为了防止该类被多次创建,需要将它的构造方法设置为私有的。

单例模式类图

image-20221118191559235

实现方法

第一步,创建网站计数器类(单例类)

网站计数器

package 设计模式.单例模式;

public class 网站计数器 {

    // 静态属性,这里使用的饿汉式实现单例
    private static 网站计数器 唯一实例 = new 网站计数器();

    private int 网站点击数;

    // 将该类的构造方法设置为私有,导致无法在外部创建对象
    private 网站计数器(){ }

    // 获取唯一实例的静态方法
    public static 网站计数器 获取唯一实例(){
        return 唯一实例;
    }

    public void 点击数增加(){
        this.网站点击数 += 1;
    }

    public int 获取点击数(){
        return this.网站点击数;
    }

}

第二步,创建测试类测试

测试类

package 设计模式.单例模式;

public class 测试类 {
    public static void main(String[] args) {
        网站计数器 计数器 = 网站计数器.获取唯一实例();
        计数器.点击数增加();
        System.out.println("当前网站点击量为:"+计数器.获取点击数());
        网站计数器 计数器二 = 网站计数器.获取唯一实例();
        计数器二.点击数增加();
        计数器二.点击数增加();
        计数器二.点击数增加();
        System.out.println("当前网站点击量为:"+计数器.获取点击数());
        网站计数器 计数器三 = 网站计数器.获取唯一实例();
        计数器三.点击数增加();
        计数器三.点击数增加();
        System.out.println("当前网站点击量为:"+计数器.获取点击数());
    }
}

实现效果

image-20221118191959145

单例模式的几种实现方法

​ 单例模式的实现方法从线程安全以及资源消耗方面来讲,有线程安全懒汉式线程不安全懒汉式饿汉式双检锁静态内部类登记式枚举式六种

线程安全的懒汉加载方法

​ 此方法以懒加载的形式创建实例,当第一次需要使用该对象时才会创建实例,以此来避免浪费内存。同时,此方法使用synchronzed 同步锁来实现线程安全,保证多个线程同时调用时只会创建单个实例,但加锁难免损耗一些性能。

演示的单例类

package 设计模式.单例模式;

public class 网站计数器 {

    /* 线程安全懒汉式创建单例 */
    
    // 用静态属性存储单例对象
    private static 网站计数器 唯一实例 ;
    private int 网站点击数;

    // 将该类的构造方法设置为私有,导致无法在外部创建对象
    private 网站计数器(){ }

    // 获取唯一实例的静态方法
    public static synchronized 网站计数器 获取唯一实例(){
        // 不存在唯一实例时先创建实例
        if (唯一实例 == null){
            唯一实例 = new 网站计数器();
        }
        return 唯一实例;
    }

    public void 点击数增加(){
        this.网站点击数 += 1;
    }

    public int 获取点击数(){
        return this.网站点击数;
    }
    
}

线程不安全的懒汉加载方法

​ 此方法与上面线程安全的懒汉加载方法基本一样,但是此方法没有添加 sycnhronized 同步锁,所以在多线程同时请求时,可能会导致创建多个对象来覆盖之间的实例对象。

演示的单例类

public class 网站计数器 {

    /* 线程不安全的懒汉式创建单例 */
    
    // 用静态属性存储单例对象
    private static 网站计数器 唯一实例 ;
    private int 网站点击数;

    // 将该类的构造方法设置为私有,导致无法在外部创建对象
    private 网站计数器(){ }

    // 获取唯一实例的静态方法
    public static 网站计数器 获取唯一实例(){
        // 不存在唯一实例时先创建实例
        if (唯一实例 == null){
            唯一实例 = new 网站计数器();
        }
        return 唯一实例;
    }

    public void 点击数增加(){
        this.网站点击数 += 1;
    }

    public int 获取点击数(){
        return this.网站点击数;
    }

}

饿汉式加载方法

​ 这种方法最为简单,同时也是线程安全的。但由于在类加载时就进行了初始化操作占据一部分内存空间,如果要过很久才会调用这个类的话,那么此类就是占着茅坑不拉屎浪费了程序的内存空间

演示的单例类

package 设计模式.单例模式;

public class 网站计数器 {
	/* 饿汉式创建单例 */
    // 静态属性,这里使用的饿汉式实现单例
    private static 网站计数器 唯一实例 = new 网站计数器();
    private int 网站点击数;

    // 将该类的构造方法设置为私有,导致无法在外部创建对象
    private 网站计数器(){ }

    // 获取唯一实例的静态方法
    public static 网站计数器 获取唯一实例(){
        return 唯一实例;
    }

    public void 点击数增加(){
        this.网站点击数 += 1;
    }

    public int 获取点击数(){
        return this.网站点击数;
    }

}

双检锁加载方法

​ 此方法优化了线程安全的懒汉式加载方法,它取消了获取实例方法上的synchronized同步锁,让多个线程获取实例时不需要排队获取,损耗性能。双检锁使用两重检查,先判断实例对象是否存在,如果已经存在则直接返回实例,而不存在实例时再通过同步锁保证线程安全的创建唯一实例。

演示的单例

package 设计模式.单例模式;

public class 网站计数器 {
	/* 双检锁创建单例 */
    // 用静态属性存储单例对象
    private static 网站计数器 唯一实例;
    private int 网站点击数;

    // 将该类的构造方法设置为私有,导致无法在外部创建对象
    private 网站计数器(){ }

    // 获取唯一实例的静态方法
    public static 网站计数器 获取唯一实例(){
        // 如果唯一实例已经创建则直接返回实例
        if (唯一实例 == null){
            // 多个线程同时准备创建实例时,使用同步锁
            synchronized(网站计数器.class){
                // 如果有线程已经创建了实例,那此线程就不再创建了
                if (唯一实例 == null){
                    唯一实例 = new 网站计数器();
                }
            }
        }
        return 唯一实例;
    }

    public void 点击数增加(){
        this.网站点击数 += 1;
    }

    public int 获取点击数(){
        return this.网站点击数;
    }

}

静态内部类登记方法

​ 该方法利用Classloader的加载机制,当类被加载时,并不会加载它的内部类,只有当显式调用该内部类时,该内部类才会被加载。通过该机制,可以实现一种线程安全的懒汉式方法创建单例对象。

演示的单例类

package 设计模式.单例模式;

public class 网站计数器 {
	 /* 静态内部类创建单例 */
    private int 网站点击数;
    static class 内部类{
        private static final 网站计数器 唯一单例 = new 网站计数器();
    }

    public static final 网站计数器 获取唯一实例(){
        // 只有外部显示调用此方法时,内部类才会被加载,唯一单例对象才会被创建
        return 内部类.唯一单例;
    }

    public void 点击数增加(){
        this.网站点击数 += 1;
    }

    public int 获取点击数(){
        return this.网站点击数;
    }

}

枚举型加载方法

​ 枚举是在jdk1.5后加入的,所以使用的人不是很多,但它却是最为便捷与简单的一种线程安全的单例创建方法。其本质是利用枚举值的唯一性与自动支持序列化的机制,杜绝在反序列化时创建新的对象。

演示的单例类

package 设计模式.单例模式;

public enum 枚举类网站计数器 {
    唯一实例;
    private int 网站点击数;

    public void 点击数增加(){
        this.网站点击数 += 1;
    }

    public int 获取点击数(){
        return this.网站点击数;
    }
}

测试类

package 设计模式.单例模式;

public class 测试类 {
    public static void main(String[] args) {
        枚举类网站计数器 计数 = 枚举类网站计数器.唯一实例;
        System.out.println("当前网站点击量为:"+计数.获取点击数());
        计数.点击数增加();
        System.out.println("当前网站点击量为:"+计数.获取点击数());
        枚举类网站计数器 计数二 = 枚举类网站计数器.唯一实例;
        计数二.点击数增加();
        计数二.点击数增加();
        计数二.点击数增加();
        System.out.println("当前网站点击量为:"+计数二.获取点击数());
        枚举类网站计数器 计数三 = 枚举类网站计数器.唯一实例;
        计数三.点击数增加();
        计数.点击数增加();
        计数三 = 计数二;
        计数三.点击数增加();
        System.out.println("当前网站点击量为:"+计数.获取点击数());
    }
}

测试效果

image-20221118203805758

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

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

相关文章

Verilog语法

Verilog语法 Verilog简介 Verilog是一种硬件描述语言,以文本形式来描述数字系统硬件的结构和行为的语言,用它可以表示逻辑电路图、逻辑表达式,还可以表示数字逻辑系统所完成的逻辑功能。 Verilog 和 C 的区别: Verilog是硬件描…

Linux篇【5】:Linux 进程概念(二)

目录 3.5、查看进程 3.6、通过系统调用接口获取时实进程的标识符 3.7、通过系统调用接口创建子进程 - fork 初识 3.5、查看进程 [HJMhjmlcc ~]$ clear [HJMhjmlcc ~]$ pwd /home/HJM [HJMhjmlcc ~]$ ls [HJMhjmlcc ~]$ touch mytest.c [HJMhjmlcc ~]$ ls mytest.c [HJMhjml…

G1D16-fraud-SVM

早上复习了一下昨天学的内容,发现这零碎时间用来复习,真的很不错。 但是遇到了一个问题:知识推理和知识挖掘有什么区别? 知识规则挖掘是对知识结构的挖掘,可以针对现有的知识体系, 利用部分规则&#xff0…

[附源码]java毕业设计冷链物流管理系统论文

项目运行 环境配置: Jdk1.8 Tomcat7.0 Mysql HBuilderX(Webstorm也行) Eclispe(IntelliJ IDEA,Eclispe,MyEclispe,Sts都支持)。 项目技术: SSM mybatis Maven Vue 等等组成,B/S模式 M…

Redis数据类型之hash

文章目录hashⅠ. 增删查改Ⅱ. 加法操作Ⅲ. 业务场景1 - 购物车Ⅳ. 业务场景2 - 秒杀Ⅴ. 注意事项提示:以下是本篇文章正文内容,Redis系列学习将会持续更新 hash ● 新的存储需求:对一系列存储的数据进行编组,方便管理,…

基于PHP的Laravel框架实现学生管理系统(1+X Web前端开发中级 例题)——初稿

文章目录 📄题目要求 🧩说明 🧩效果图 💻题目代码 🎯实现效果 📰完整答案 📄题目要求 阅读下列说明、效果图和代码,补全代码(1)-(10&…

如何“0基础”备考CISSP一次通过?

2019年进入网络安全行业的PM,苦恼于行业与业务认知存在较大的知识差距,恰好公司一系列政策鼓励员工学习网安等专业知识,和报考相关专业认证,通过了解,最终决定「挑战」CISSP! 选择CISSP的原因 CISSP八个域…

第2-3-6章 打包批量下载附件的接口开发-文件存储服务系统-nginx/fastDFS/minio/阿里云oss/七牛云oss

文章目录5.6 接口开发-根据文件id打包下载附件5.6.1 接口文档5.6.2 代码实现5.6.3 接口测试5.7 接口开发-根据业务类型/业务id打包下载5.7.1 接口文档5.7.2 代码实现5.7.3 接口测试5.6 接口开发-根据文件id打包下载附件 第2-1-2章 传统方式安装FastDFS-附FastDFS常用命令 第2-…

LeetCode——Weekly Contest 319

LeetCode周赛第319场记录 这场周赛的质量也很高&#xff0c;有很多值得学习的地方。 2469. 温度转换 这道题很简单&#xff0c;直接根据已有的信息转换即可&#xff0c;一行代码搞定&#xff0c;注意公式不要敲错。 class Solution { public:vector<double> convertTem…

【案例 5-2】 模拟默认密码自动生成

【案例介绍】 1.任务描述 本例要求编写一个程序&#xff0c;模拟默认密码的自动生成策略&#xff0c;手动输入用户名&#xff0c;根据用户名 自动生成默认密码。在生成密码时&#xff0c;将用户名反转即为默认的密码。 2.运行结果 运行结果如图 5-2 所示。 图 5-2 模拟密码自…

[附源码]java毕业设计企业公开招聘系统

项目运行 环境配置&#xff1a; Jdk1.8 Tomcat7.0 Mysql HBuilderX&#xff08;Webstorm也行&#xff09; Eclispe&#xff08;IntelliJ IDEA,Eclispe,MyEclispe,Sts都支持&#xff09;。 项目技术&#xff1a; SSM mybatis Maven Vue 等等组成&#xff0c;B/S模式 M…

Leetode-891-子序列宽度之和

1、数学 因为我们需要求得是子序列的宽度之和&#xff0c;我们可以先确定不同宽度对应的子序列的个数&#xff0c;而后将其相加即可。我们可以首先在子序列中固定最大值和最小值&#xff0c;此时在剩余的n−2n-2n−2个数中我们可以依次选择0或1或2一直到n−2n-2n−2个&#xf…

简单工厂、工厂方法 、抽象工厂模式之间的联系

概念 1.简单工厂: 简单工厂模式中工厂为具体工厂&#xff0c;产品为抽象产品&#xff0c;由工厂实例创建产品实例。 2.工厂方法模式&#xff1a; 定义一个用于创建对象的接口&#xff0c;让其子类确定实例化哪一个类。工厂方法使一个类的实例化延迟到其子类。 3.抽象工厂模式&…

Docker中部署elasticsearch

Docker中部署elasticsearch1.下载镜像2.查看镜像是否下载成功3.选择挂载硬盘创建三个目录&#xff0c;分别对应es的日志&#xff0c;es的配置&#xff0c;和es的插件。并设置目录权限为可读写。4.进入config目录里创建elasticsearch.yml文件&#xff0c;并使用vi命令插入如下内…

windows下app爬虫环境搭建:python + fiddler + Appium + 夜神模拟器

夜神模拟器自行下载安装 1、安装java https://www.oracle.com/java/technologies/downloads/#jdk19-windows 配置环境变量 系统环境下 在Path下添加 验证安装情况 打开 cmd 命令行工具&#xff0c;输入 java -version&#xff0c;返回信息如图所示表示安装配置成功 …

11.18MyBatis 学习2

1 #和$的区别 #{}表示一个占位符号 通过#{}可以实现 preparedStatement 向占位符中设置值&#xff0c;自动进行 java 类型和 jdbc 类型转换&#xff0c;#{}可以有效防止 sql 注入。 #{}可以接收简单类型值或 pojo 属性值。可以自动对值添加 ’ ’ 单引号 ${}表示拼接 sql 串…

【附源码】Python计算机毕业设计天气预报查询管理系统

项目运行 环境配置&#xff1a; Pychram社区版 python3.7.7 Mysql5.7 HBuilderXlist pipNavicat11Djangonodejs。 项目技术&#xff1a; django python Vue 等等组成&#xff0c;B/S模式 pychram管理等等。 环境需要 1.运行环境&#xff1a;最好是python3.7.7&#xff0c;…

linux x64 下的redis安装

1.官网下载linux版本的安装包&#xff0c;官网地址&#xff1a;http://redis.io/download 2.新建目录 mkdir /usr/local/src/redis&#xff0c;并上传到该目录&#xff0c;解压 tar xzvf redis-7.0.5.tar.gz 3…执行make 对Redis解压后文件进行编译 在编译完成之后查看当前文…

MeterSphereV2.3版本Mac本地启动详细教程(含常见错误)

一、准备工作 因为代码是java语言写的&#xff0c;所以提前准备好java的环境JDK11 &#xff1a;https://www.oracle.com/java/technologies/javase/jdk11-archive-downloads.htmlmaven&#xff08;3.6.2以上都行&#xff09;&#xff1a;https://archive.apache.org/dist/mave…

进程的虚拟地址空间

每个程序运行起来后&#xff0c;都拥有一个自己的虚拟地址空间&#xff08;注意是虚拟的&#xff0c;不是实际存在的&#xff09;&#xff0c;这个虚拟地址空间的大小由计算机的硬件平台 关于虚拟的概念&#xff0c;当时IBM给出了一种说法很形象生动&#xff1a; 它存在&#x…