Java软件设计模式-单例设计模式

news2024/9/21 11:14:56

目录

1.软件设计模式的概念

2.设计模式分类

2.1 创建型模式

2.2 结构型模式

2.3 行为型模式

3.单例设计模式

3.1 单例模式的结构

 3.2 单例模式的实现

 3.2.1 饿汉式-方式1(静态变量方式)

 3.2.2 懒汉式-方式1(线程不安全)

3.2.3 懒汉式-方式2(线程安全)

3.3 单例模式的优点和缺点


1.软件设计模式的概念

软件设计模式(Software Design Pattern),又称设计模式,是一套被“反复使用”、“多数人知晓的”、“代码设计经验的总结”。它描述了在软件设计过程中的一些不断重复发生的问题,以及该问题的解决方案。也就是说,它是解决特定问题的一系列套路,是“前辈们的代码设计经验的总结”,具有一定的普遍性,可以反复使用。

2.设计模式分类

2.1 创建型模式

用于描述“怎样创建对象”,它主要特点是“将对象的创建与使用分离”。GoF书中提供了单例、原型、工厂方法、抽象工厂、建造者五种创建型模式。

2.2 结构型模式

用于描述如何将类或对象按某种布局组成更大的结构,GoF书中提供了代理、适配器、桥接、装饰、外观、享元、组合等 7 种结构型模式。

2.3 行为型模式

用于描述类或对象之间怎样相互协作共同完成单个对象无法单独完成的任务,以及怎样分配职责。GoF(四人组)书中提供了模板方法、策略、命令、职责链、状态、观察者、中介者、迭代器、访问者、备忘录、解释器11 种行为型模式。

3.单例设计模式

  • 单例模式(Singleton Pattern)是 Java 中最简单的设计模式之一。这种类型的设计模式属于创建型模式,它提供了一种创建对象的最佳方式。
  • 这种模式涉及到一个单一的类,该类负责创建自己的对象,同时确保只有单个对象被创建。这个类提供了一种访问其唯一的对象的方式,可以直接访问,不需要实例化该类的对象。

3.1 单例模式的结构

单例模式的主要有以下角色:

  • 单例类。只能创建一个实例的类.
  • 访问类。使用单例类

 3.2 单例模式的实现

单例模式的设计分为俩种:

  • 饿汉式:类加载就会导致该单实例对象被创建。
  • 懒汉式:类加载不会导致该单实例对象被创建,而是首次使用该对象时才会被创建。

 3.2.1 饿汉式-方式1(静态变量方式)

/**
 * 饿汉式
 *      静态变量创建类的对象
 */
public class Singleton {
    //私有构造方法
    private Singleton() {}

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

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

说明:

该方式在成员位置声明Singleton类型的静态变量,并创建Singleton类的对象instance。instance对象是随着类的加载而创建的。如果该对象足够大的话,而一直没有使用就会造成内存的浪费。

 3.2.2 懒汉式-方式1(线程不安全)

/**
 * 懒汉式
 *  线程不安全
 */
public class Singleton {
    //私有构造方法
    private Singleton() {}

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

    //对外提供静态方法获取该对象
    public static Singleton getInstance() {

        if(instance == null) {
            instance = new Singleton();
        }
        return instance;
    }
}

说明:

从上面代码我们可以看出该方式在成员位置声明Singleton类型的静态变量,并没有进行对象的赋值操作,那么什么时候赋值的呢?当调用getInstance()方法获取Singleton类的对象的时候才创建Singleton类的对象,这样就实现了懒加载的效果。但是,如果是多线程环境,会出现线程安全问题。

3.2.3 懒汉式-方式2(线程安全)

/**
 * 懒汉式
 *  线程安全
 */
public class Singleton {
    //私有构造方法
    private Singleton() {}

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

    //对外提供静态方法获取该对象
    public static synchronized Singleton getInstance() {

        if(instance == null) {
            instance = new Singleton();
        }
        return instance;
    }
}

说明:

该方式也实现了懒加载效果,同时又解决了线程安全问题。但是在getInstance()方法上添加了synchronized关键字,导致该方法的执行效果特别低。从上面代码我们可以看出,其实就是在初始化instance的时候才会出现线程安全问题,一旦初始化完成就不存在了。

3.2.4 懒汉式-方式3(双重检查锁)

再来讨论一下懒汉模式中加锁的问题,对于 getInstance() 方法来说,绝大部分的操作都是读操作,读操作是线程安全的,所以我们没必让每个线程必须持有锁才能调用该方法,我们需要调整加锁的时机。由此也产生了一种新的实现模式:双重检查锁模式。

/**
 * 双重检查方式
 */
public class Singleton { 

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

    private static Singleton instance;

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

说明:

双重检查锁模式是一种非常好的单例实现模式,解决了单例、性能、线程安全问题,上面的双重检测锁模式看上去完美无缺,其实是存在问题,在多线程的情况下,可能会出现空指针问题,出现问题的原因是JVM在实例化对象的时候会进行优化和指令重排序操作。

要解决双重检查锁模式带来空指针异常的问题,只需要使用 volatile 关键字, volatile 关键字可以保证可见性和有序性。

/**
 * 双重检查方式
 */
public class Singleton {

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

    private static volatile Singleton instance;

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

小结:

添加 volatile 关键字之后的双重检查锁模式是一种比较好的单例实现模式,能够保证在多线程的情况下线程安全也不会有性能问题。

还有,这里的private static volatile Singleton singleton = null;中的volatile也必不可少,volatile关键字可以防止jvm指令重排优化。

在java内存模型中,volatile 关键字作用可以是保证可见性或者禁止指令重排。这里是因为 singleton = new Singleton() ,它并非是一个原子操作,事实上,在 JVM 中上述语句至少做了以下这 3 件事:

  • 第一步是给 singleton 分配内存空间;

  • 第二步开始调用 Singleton 的构造函数等,来初始化 singleton;

  • 第三步,将 singleton 对象指向分配的内存空间(执行完这步 singleton 就不是 null 了)。

这里需要留意一下 1-2-3 的顺序,因为存在指令重排序的优化,也就是说第 2 步和第 3 步的顺序是不能保证的,最终的执行顺序,可能是 1-2-3,也有可能是 1-3-2。

如果是 1-3-2,那么在第 3 步执行完以后,singleton 就不是 null 了,可是这时第 2 步并没有执行,singleton 对象未完成初始化,它的属性的值可能不是我们所预期的值。假设此时线程 2 进入 getInstance 方法,由于 singleton 已经不是 null 了,所以会通过第一重检查并直接返回,但其实这时的 singleton 并没有完成初始化,所以使用这个实例的时候会报错,详细流程如下图所示:

 这里还说一下volatile关键字的第二个作用,保证变量在多线程运行时的可见性:

public class Test01 {
    public static void main(String[] args) throws Exception{
        T t=new T();
        t.start();

        Thread.sleep(2000);
        System.out.println("主线程设置t线程的参数来止损失");
        t.setFlag(false);
    }
}
class T extends Thread{
    private volatile boolean flag=true;

    public void setFlag(boolean flag) {
        this.flag = flag;
    }

    @Override
    public void run() {
        System.out.println("进入run方法");
        while(flag){

        }
    }
}

 在 JDK1.2 之前,Java的内存模型实现总是从主存(即共享内存)读取变量,是不需要进行特别的注意的。而在当前 的 Java 内存模型下,线程可以把变量保存本地内存(比如机器的寄存器)中,而不是直接在主存中进行读写。这就 可能造成一个线程在主存中修改了一个变量的值,而另外一个线程还继续使用它在寄存器中的变量值的拷贝,造成数 据的不一致。 要解决这个问题,就需要把变量声明为 volatile,这就指示 JVM,这个变量是不稳定的,每次使用它都到主存中进行 读取。

3.3 单例模式的优点和缺点

优点:单例类只有一个实例,节省了内存资源,对于一些需要频繁创建销毁的对象,使用单例模式可以提高系统性能;单例模式可以在系统设置全局的访问点,优化和共享数据,例如前面说的Web应用的页面计数器就可以用单例模式实现计数值的保存。

缺点:单例模式一般没有接口,扩展的话除了修改代码基本上没有其他途径。

下一篇:软件设计模式-工厂模式-CSDN博客

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

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

相关文章

【linux】安装cuda11.0、cuDNN教程,简单易懂,包教包会

【linux】安装cuda11.0、cuDNN教程,简单易懂,包教包会 【创作不易,求点赞关注收藏】 文章目录 【linux】安装cuda11.0、cuDNN教程,简单易懂,包教包会一、版本情况介绍二、安装cuda1、到官网找到对应版本进行安装2、对…

【openwrt】Openwrt系统新增普通用户指南

文章目录 1 如何新增普通用户2 如何以普通用户权限运行服务3 普通用户如何访问root账户的ubus服务4 其他权限控制5 参考 Openwrt系统在默认情况下只提供一个 root账户,所有的服务都是以 root权限运行的,包括 WebUI也是通过root账户访问的,…

使用EndNote添加参考文献,如何区分中英文文献的et al和等?

一、背景 我们在用EndNote添加参考文献时,如遇到超过3个作者,需列出前三位作者,其余用“et al”代替。 但中文文献用“et al”显示不合适,如下图所示,需要用“等”代替。 二、中文参考文献大于3个作者,用等…

5G数字化转型redcap助您“轻”装上阵

RedCap(Reduced Capability)技术,也称为NR-Light,是针对5G网络的一种轻量化技术规范,旨在为具有较低性能要求的设备提供5G连接。 RedCap技术特点 低成本 降低芯片组和设备成本:RedCap通过减少终端带宽、收…

【Playwright+Python】系列 Pytest 插件在Playwright中的使用

一、命令行使用详解 使用 Pytest 插件在Playwright 中来编写端到端的测试。 1、命令行执行测试 pytest --browser webkit --headed 2、使用 pytest.ini 文件配置 内容如下: [pytest] # Run firefox with UIaddopts --headed --browser firefox效果&#xff1…

STM32入门开发操作记录(三)——按键控制LED

目录 一、模块化二、LED交替闪烁1. LED.c2. LED.h3. 主函数 三、按键控制LED1. Key.c2. Key.h3. LED.c4. LED.h5. 主函数 一、模块化 前篇介绍了如何向项目添加模块,本篇将进一步介绍模块的编写与封装。随着模块的增加,需要用到Manage Project Items&…

多终端文件互传

LocalSend - 下载下载LocalSend适用于Windows、macOS、Linux、Android和iOS。https://localsend.org/zh-CN/download

C语言 ——— 输入两个正整数,求出最小公倍数

目录 何为最小公倍数 题目要求 代码实现 方法一:暴力求解法(不推荐) 方法二:递乘试摸法(推荐) 何为最小公倍数 最小公倍数是指两个或者多个正整数(除了0以外)的最小的公共倍数…

浅谈RLHF---人类反馈强化学习

浅谈RLHF(人类反馈强化学习) RLHF(Reinforcement Learning fromHuman Feedback)人类反馈强化学习 RLHF是[Reinforcement Learning from Human Feedback的缩写,即从人类反馈中进行强化学习。这是一种结合了机器学习中…

Android Toast

Toast Toast是Android常用的简单控件,主要用来进行简短的信息提示,如图1所示。 图1 Toast效果图 Toast的基本用法很简单,不需要设置layout,只需要在程序中调用即可。Toast调用makeText()方法设置需要显示的界面、显示的内容、显…

简洁实用的原创度检测工具AntiPlagiarism NET 4.132

AntiPlagiarism NET是一个适用于Windows的程序,它允许您检查文本的唯一性和从不同Internet来源借用的存在。使用AntiPlagiarism NET,您可以: 将程序用于不同的目的该程序适用于学生、教师、记者、文案作者和其他需要检查其文本或其他作者文本…

SpringBoot实战:多表联查

1. 保存和更新公寓信息 请求数据的结构 Schema(description "公寓信息") Data public class ApartmentSubmitVo extends ApartmentInfo {Schema(description"公寓配套id")private List<Long> facilityInfoIds;Schema(description"公寓标签i…

【2024最新华为OD-C/D卷试题汇总】[支持在线评测] 游乐园门票 (200分) - 三语言AC题解(Python/Java/Cpp)

&#x1f36d; 大家好这里是清隆学长 &#xff0c;一枚热爱算法的程序员 ✨ 本系列打算持续跟新华为OD-C/D卷的三语言AC题解 &#x1f4bb; ACM银牌&#x1f948;| 多次AK大厂笔试 &#xff5c; 编程一对一辅导 &#x1f44f; 感谢大家的订阅➕ 和 喜欢&#x1f497; 最新华为O…

4000厂商默认账号密码、默认登录凭证汇总.pdf

获取方式&#xff1a; 链接&#xff1a;https://pan.baidu.com/s/1F8ho42HTQhebKURWWVW1BQ?pwdy2u5 提取码&#xff1a;y2u5

C语言 ——— 调试的时候如何查看当前程序的变量信息

目录 调试前/后的调试窗口 ​编辑 调试窗口 --- 监视 调试窗口 --- 内存 调试窗口 --- 调用堆栈 调试前/后的调试窗口 调试前的调试窗口&#xff1a; 调试前的调试窗口是没有显示的&#xff0c;只有在调试的时候才会有相对应的调试窗口 调试后的调试窗口&#xff1a…

头歌资源库(31)象棋中马遍历棋盘的问题

一、 问题描述 二、算法思想 这是一个典型的深度优先搜索问题。 首先&#xff0c;我们创建一个mn的棋盘&#xff0c;并初始化所有的点为未访问状态。 然后&#xff0c;我们从(0, 0)位置开始进行深度优先搜索。 在每一步中&#xff0c;我们先标记当前位置为已访问&#xff0…

垃圾收集篇

文章目录 垃圾收集算法垃圾的概念对象存活的判断引用计数器法可达性分析算法 算法标记清除算法复制算法标记压缩算法 垃圾收集的相关概念STW安全点安全区域 垃圾收集器重要指标吞吐量停顿时间 垃圾收集器的分类Serial 收集器&#xff1a;串行回收ParNew 收集器&#xff1a;并行…

数据结构——查找(线性表的查找与树表的查找)

目录 1.查找 1.查找的基本概念 1.在哪里找&#xff1f; 2.什么查找&#xff1f; 3.查找成功与否&#xff1f; 4.查找的目的是什么&#xff1f; 5.查找表怎么分类&#xff1f; 6.如何评价查找算法&#xff1f; 7.查找的过程中我们要研究什么&#xff1f; 2.线性表…

【周末闲谈】Stable Diffusion会魔法的绘画师

个人主页&#xff1a;【&#x1f60a;个人主页】 系列专栏&#xff1a;【❤️Python】 文章目录 前言Stable Diffusion介绍 使用ComfyUI 和 WebUIComfyUIWebUI 配置需求 Stable Diffusion资源分享吐司AiAUTOMATIC1111Civitai绘世整合包Nenly同学stability.ai 前言 在很早之前&…

2-33 基于matlab的用于计算无故障的斜齿轮对啮合时接触线长度随时间的变化

基于matlab的用于计算无故障的斜齿轮对啮合时接触线长度随时间的变化&#xff0c;根据需求设置斜齿轮对的相应参数&#xff0c;得到结果。程序已调通&#xff0c;可直接运行。 2-33 斜齿轮对啮合时接触线长度 齿轮参数 - 小红书 (xiaohongshu.com)