设计模式之创建型模式---单例模式

news2024/10/6 10:38:57

在这里插入图片描述

文章目录

  • 1.介绍
  • 2.应用场景
  • 3.实现
    • 3.1 结构
    • 3.2 类图
    • 3.3 代码示例
      • 3.3.1 饿汉式
      • 3.3.2 懒汉式
      • 3.3.3 双重检验锁
      • 3.3.3 静态内部类实现单例
      • 3.3.4 枚举类实现单例
  • 总结

1.介绍

单例模式(singleton) 是指某个类中能生成一个实例,该类提供了一个全局访问点,提供一个唯一的实例给外部调用,这样做的目的是为了节省资源,减少垃圾回收的消耗,保证数据的一致性,对某些类要求只能创建一个实例(对象)。

2.应用场景

单例模式的应用场景有数据库的连接池,应用程序中的对话框,系统中的缓存,多线程中的线程池等

3.实现

3.1 结构

单例模式主要有两个角色,一个是实现了单例的类,另一个是使用单例的类。

单例类: 单例类就是实现单例的类,也就是这个类中提供了一个方法给外部用于获取这个类的实例,这个实例在这个方法中会做唯一性判断,即这个类的对象如果创建过了,就直接使用,否则再创建。
访问类: 使用单例的类,也就是客户端类。通俗说就是我们要使用这个单例类对象的地方

3.2 类图

在这里插入图片描述注:类图不了解的小伙伴要自己去了解哦,这里不做扩展了

3.3 代码示例

3.3.1 饿汉式

饿汉式:指的是类加载的时候就进行初始化

public class Singleton {
    
    //声明一个私有的本类对象
    private static Singleton INSTANCE = new Singleton();
    
    //将构造函数私有化,让外部调用者只能通过我们提供的方法创建对象
    private Singleton(){}

    //饿汉式
    public static Singleton getInstance(){
        return INSTANCE;
    }

    public void testFun(){
        System.out.println("测试单例模式");
    }
}

public class Client {
    public static void main(String[] args) {
        Singleton instance1 = Singleton.getInstance();
        Singleton instance2 = Singleton.getInstance();
        System.out.println("instance1: " + instance1  +  " ,instance2: " + instance2);
        instance1.testFun();
    }
}

优点: 线程安全,因为JVM在加载这个类的时候就会进行初始化,包括对静态变量的初始化。
缺点: 空间浪费,饿汉式是使用空间换时间,不判断直接创建,假设创建了后不使用这个对象,就造成了空间浪费。如果单例类的体积比较大的话,空间的浪费也是不容忽视的。

运行结果

在这里插入图片描述

3.3.2 懒汉式

懒汉式:指的是在使用实例的时候再进行初始化,但是这种方式在多线程情况下使用会有问题,即这种方式是线程不安全的

public class Singleton {

    //声明一个私有的本类对象
    private static Singleton INSTANCE =  null;
    
    //将构造函数私有化,让外部调用者只能通过我们提供的方法创建对象
    private Singleton(){}

    public static Singleton getInstance(){
        if(INSTANCE == null){
            INSTANCE = new Singleton();
        }
        
        return INSTANCE;
    }

    public void testFun(){
        System.out.println("测试单例模式");
    }
}

优点:节省空间,使用的时候才会创建实例对象。
缺点:线程不安全。

3.3.3 双重检验锁

双重检验锁是对懒汉式的改进,使其可以在多线程的场景中使用

public class Singleton {

    //声明一个私有的本类对象
    private volatile static Singleton INSTANCE =  null;

    //将构造函数私有化,让外部调用者只能通过我们提供的方法创建对象
    private Singleton(){}

    public static Singleton getInstance(){
        //先判断实例是否存在
        if(INSTANCE == null){
            //加锁创建实例
            synchronized (Singleton.class){
                //再次判断,因为可能会出现某个线程拿到锁后,还没来得及执行初始化就释放了锁,
                //这时假如其他线程拿到了锁又执行到了这里的话会创建一个实例,这样就会出现多个实例
                if(INSTANCE == null){
                    INSTANCE = new Singleton();
                }
            }
        }

        return INSTANCE;
    }

    public void testFun(){
        System.out.println("测试单例模式");
    }
}

这里我们可以看到有几个改进,首先是声明本类的实例时加了一个voltile 关键字;
private volatile static Singleton INSTANCE = null;
因为jvm创建对象的过程不是原子的,步骤如下。
① 在堆内存中, 为新的实例开辟空间;
② 初始化构造器, 对实例中的成员进行初始化;
③ 把这个实例的引用 (也就是这里的instance) 指向①中空间的起始地址.
如果1-3步骤不是原子性的,那么在创建对象的过程中,jvm可能会做指令优化,也就是对1-3的顺序做重排序,比如2在1的前面。这样就会导致创建出来的对象是不完整的,是无法使用的。而且还不好定位这个问题,所以volatile关键字的作用就是可以禁止jvm做指令重排序

3.3.3 静态内部类实现单例

静态内部类的方式是线程安全的,并且是懒加载的方式,即使用的时候才会去创建类的对象,是懒汉式的变形

注意: jvm 加载类的时候:步骤为: 加载 -> 验证-> 准备 -> 解析 -> 初始化,并且JVM在加载外部类的过程中,不会加载静态内部类,只有内部类的属性/方法被调用的时候才会被加载,并初始化静态属性

public class Singleton {
    //将构造函数私有化,让外部调用者只能通过我们提供的方法创建对象
    private Singleton(){}

    public static Singleton getInstance(){
        return SingletonHolder.INSTANCE;
    }
    
    private static class SingletonHolder{
        private static Singleton INSTANCE = new Singleton();
    }
    public void testFun(){
        System.out.println("测试单例模式");
    }
}

静态内部类实现单例
优点:不加锁,线程安全,用到的时候才会加载,并发性能高,推荐使用

3.3.4 枚举类实现单例

JDK5 开始,提供了枚举,枚举其实是一个语法糖,让我们可以少写一些代码,JVM编译的时候会帮我们添加很多额外的信息,枚举类可以在JVM层面保证线程安全

enum Singleton{
    INSTANCE;//枚举类使用单例可以直接使用Singleton.INSTANCE 获取单例使用

    public void testFun(){
        System.out.println("枚举类实现单例");
    }
}

//枚举单例使用方法
public class Client {
    public static void main(String[] args) {
        Singleton instance1 = Singleton.INSTANCE;
        Singleton instance2 = Singleton.INSTANCE;
        System.out.println("instance1: " + instance1  +  " ,instance2: " + instance2);
        instance1.testFun();
    }
}

优点: 不需要考虑序列化的问题:枚举序列化是由JVM保证的, 每一个枚举类型和枚举变量在JVM中都是唯一的, 在枚举类型的序列化和反序列化上Java做了特殊的规定: 在序列化时Java仅仅是将枚举对象的name属性输出到结果中, 反序列化时只是通过java.lang.Enum#valueOf()方法来根据名字查找枚举对象 —— 编译器不允许对这种序列化机制进行定制、并且禁用了writeObject、readObject、readObjectNoData、writeReplace、readResolve等方法, 从而保证了枚举实例的唯一性;

不需要考虑反射的问题: 在通过反射方法

java.lang.reflect.Constructor#newInstance()//创建枚举实例时, JDK源码对调用者的类型进行了判断:

// 判断调用者clazz的类型是不是Modifier.ENUM(枚举修饰符), 如果是就抛出参数异常:
if ((clazz.getModifiers() & Modifier.ENUM) != 0)
    throw new IllegalArgumentException("Cannot reflectively create enum objects");

所以, 我们是不能通过反射创建枚举实例的, 也就是说创建枚举实例只有编译器能够做到.保证了安全
缺点: 所有的属性都必须在创建时指定, 也就意味着不能延迟加载; 并且使用枚举时占用的内存比静态变量的2倍还多, 这在性能要求严苛的应用中是不可忽视的.


总结

本节主要介绍了单例的几种创建方式,推荐使用静态内部类的方式,也可以使用双重检验锁的方式。在开发中也是这两种方式使用得最多。读者还有其他好的方式的话可以评论区讨论

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

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

相关文章

JUC并发编程与源码分析笔记07-volatile与JMM

被volatile修饰的变量有两大特点 可见性、有序性,但是不保证原子性。 当写一个volatile变量时,JMM会把该线程对应的本地内存中的共享变量值立即刷新回主内存中。当读一个volatile变量时,JMM会把该线程对应的本地内存设置为无效,重…

Java开发:汇编语言

一、为什么学习汇编语言 中国人和中国人沟通需要学习汉语 中国人和美国人沟通在会汉语的基础上还要学习英语 那么,人和机器沟通的话需要学习哪些语言呢? 答案是:人类的语言机器语言 但是,机器语言都是由0和1组成,人类…

opencv图像直方图

灰度直方图:从数学上来说,图像直方图是描述图像的各个灰度级的统计特性,它是图像灰度值的函数,统计图像中各个灰度级出现的次数或频率。从图像上来说,灰度直方图是一个二维图像,横坐标为图像中各个像素点的…

git stash命令用法详解(临时存储代码)

1、需求背景 有时候在开发过程中,在一个分支上(dev1)已经写了一部分代码,但是需要紧急切换到别的分支(dev2)上修改某个代码,这时候不能直接从dev1分支上切换到dev2分支上,提示你需要保存代码。此时dev1分支…

Linux内存模型

sparse内存模型前言1.SPARSEMEM原理:2.vmemmap在虚拟地址空间位置3.virt,phys,page,pfn之间的转换关系3.1内核态虚拟地址和物理内存地址转换关系3.2页帧pfn、物理内存的page指针的关系3.3其他快捷的转换总结前言 Linux中的物理内存被按页框划…

408 考研《操作系统》第二章第五节:信号量机制和用信号量机制实现进程互斥、同步、前驱关系

文章目录教程1. 信号量机制1.1 概念1.2 信号量机制——整型信号量1.3 信号量机制——记录型信号量(important)1.4 总结2. 用信号量机制实现进程互斥、同步、前驱关系2.1 信号量机制实现进程互斥(important)2.2 信号量机制实现进程…

java基于SpringBoot的在线答疑系统的研究与实现-计算机毕业设计

项目介绍 社会的发展和科学技术的进步,互联网技术越来越受欢迎。网络计算机的生活方式逐渐受到广大师生的喜爱,也逐渐进入了每个学生的使用。互联网具有便利性,速度快,效率高,成本低等优点。 因此,构建符合…

【matplotlib】2-使用统计函数绘制简单图形

文章目录使用统计函数绘制简单图形1.函数bar()--用于绘制柱状图2.函数barh()--用于绘制条形图3.函数hist()--用于绘制条形图4.函数pie()--用于绘制饼图5.函数polar()--用于绘制极线图6.函数scatter()--用于绘制气泡图7.函数stem()--用于绘制棉棒图8.函数boxplot()--用于绘制箱线…

openGauss洗冤录 之 copy from

openGauss洗冤录 之 copy from 引子 之前一篇《测评报告:文件导入哪家强?》关于openGauss性能与预期不符的问题留下了个坑,今天回来填坑。 前文提到使用openGauss的copy from导入csv文件耗时是mysql的2倍,是PostgreSQL的6倍&#…

下载nacos-server-1.1.4安装包,使用mvn打包

git官方地址下载nacos-server-1.1.4.zip速度太慢,码云上下载地址没有安装包。采用从码云上下载源码,自行打包。(https://gitee.com/mirrors/Nacos/tree/1.1.4)下载完成之后,进入项目目录如图 下载源码后 ,解压 本地在本文件夹 在d…

这十套练习,教你如何用Pandas做数据分析(03)

练习3-数据分组 探索酒类消费数据 步骤1 导入必要的库 运行以下代码 import pandas as pd 步骤2 从以下地址导入数据 运行以下代码 path3 ‘…/input/pandas_exercise/pandas_exercise/exercise_data/drinks.csv’ #‘drinks.csv’ 步骤3 将数据框命名为drinks 运行以下代…

SpringBoot+Vue实现前后端分离的航空售票管理系统

文末获取源码 开发语言:Java 使用框架:spring boot 前端技术:JavaScript、Vue.js 、css3 开发工具:IDEA/MyEclipse/Eclipse、Visual Studio Code 数据库:MySQL 5.7/8.0 数据库管理工具:phpstudy/Navicat JD…

JavaScript(一):编写位置、输入输出语句

JavaScript入门一、 JavaScript编写位置二、输入输出语句一、 JavaScript编写位置 (1)编写到script标签中 控制浏览器弹出一个警告框 alert("警告内容")让计算机在页面中输出一个内容 document.write() /*可以向body中输出一个内容*/向控制…

基于java+springboot+mybatis+vue+mysql的自媒体社区平台

项目介绍 近几年来自媒体平台的发展越来越迅猛,并逐渐成为新闻信息传播的主流模式,自媒体平台的内容构成没有主要的核心,新闻信息的探讨和传播环境比较自由,与此同时自媒体平台概念的应用与发展,赋予了普通民众发表自己感想的权利…

OneUI 5.5.0 for HTML/PHP/VueJS

OneUI 是一个高度通用的 Bootstrap 管理仪表板模板和 UI 框架,支持 Laravel,可让您以相同的速度和稳健的布局创建各种网站。它是使用 Sass 和 ECMAScript 6 (ES6) 开发的,并为开发人员提供了各种智能工具,如 webpack5、Babel 7、G…

如何避免SCI写作中的中式思维以及无处不在的Chinglish

现在随着全球经济的下行,很多小伙伴都选择了延时就业,因此更多的人开始考研和考博。新东方大学生学习与发展中心发布的《新东方2023考研报告》预测:2023考研报名将超过520万人。俗话说(我道听途说的):一入科…

一文教你数据结构体栈和队列的实现

前言: 关于c语言的学习已经差不多更新完毕,如果发现个别知识点,我还会继续更新,但目前已经准备往c和数据结构的重心挪动,这篇文章就是向大家讲述数据结构中栈和队列的实现。 💞 💞 欢迎来到小…

VoIP通话-基于SIP协议的Asterisk(一)-实现流程

文章首发及后续更新:https://mwhls.top/4122.html,无图/无目录/格式错误/更多相关请至首发页查看。 新的更新内容请到mwhls.top查看。 欢迎提出任何疑问及批评,非常感谢! VoIP通话-基于SIP协议的Asterisk该篇仅包含实现流程&#…

实验12 动态查找2022

A. DS二叉排序树之创建和插入 给出一个数据序列,建立二叉排序树,并实现插入功能 对二叉排序树进行中序遍历,可以得到有序的数据序列 输入 第一行输入t,表示有t个数据序列 第二行输入n,表示首个序列包含n个数据 第…

Vue 基础详解 | 系统性学习 | 无知的我费曼笔记

无知的我正在复盘Vue 该笔记特点是 重新整理了涉及资料的一些语言描述、排版而使用了自己的描述对一些地方做了补充说明。比如解释专有名词、类比说明、对比说明、注意事项提升了总结归纳性。尽可能在每个知识点上都使用一句话 || 关键词概括更注重在实际上怎么应用提出并回答…