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

目录

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) {//抢到锁之后再次判断是否为nullif(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;}@Overridepublic void run() {System.out.println("进入run方法");while(flag){}}
}

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

3.3 单例模式的优点和缺点

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

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

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

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

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

相关文章

职场新人感受

互联网职场感受 阶段介绍 24届6月底毕业生,之前从未实习过。 岗位是后端开发(JAVA),目前已经上班三周(前两周看文档和做了半个简单需求,第三周脱产新人培训)。 职场体验 职场和想象中的工作…

c++ 网络编程udp协议 poco模块

官网资料(需要梯子):https://pocoproject.org/slides/200-Network.pdf 1、poco是在原生socket之上的封装,底层还是socket,性能低于socket,安全性和实用性高于socket,即使用简便,接口简单 2、udp协议是&…

办公灯多普勒雷达模组感应开关,飞睿智能24G毫米波雷达超低功耗uA级,节能LED灯新搭档

在科技日新月异的今天,节能、环保已经成为我们生活和工作中不可或缺的一部分。作为新时代的办公人,我们不仅要追求高效的工作方式,更要关注我们所使用的设备是否足够环保、节能。今天,我们就来聊聊一个令人兴奋的创新——飞睿智能…

COMX-P2020、COMX-P1022 vxWorks系统开发主机

提供vxworks6.9开发环境和BSP源码,具有千兆以太网,调试串口,4个PCIe插槽,支持PCIe 1.0a和msi中断,底板板载一块Xilinx CPLD XC95144,提供ISE14.7安装包和verilog源码。可定制开发基于PCIe接口和fpga的拓展接…

多语言环境大师:在PyCharm中管理多个Python解释器

多语言环境大师:在PyCharm中管理多个Python解释器 PyCharm作为业界领先的Python集成开发环境(IDE),支持多种Python解释器的配置和管理,使得开发者可以针对不同项目使用不同的Python环境。本文将详细介绍如何在PyCharm…

如何30分钟下载完368G的Android系统源码?

如何30分钟下载完368G的Android系统源码? Android系统开发的一个痛点问题就是Android系统源码庞大,小则100G,大则,三四百G。如标题所言,本文介绍通过局域网高速网速下载源码的方法。 制作源码mirror 从源码git服务器A&#xff0c…

推荐系统:从协同过滤到深度学习

目录 一、协同过滤(Collaborative Filtering, CF)1. 基于用户的协同过滤2. 基于物品的协同过滤 二、深度学习在推荐系统中的应用1. 深度学习模型的优势2. 深度学习在推荐系统中的应用实例 三、总结与展望 推荐系统是现代信息处理和传播中不可或缺的技术&…

【话题】破茧而出:打破AI“信息茧房”,捍卫信息自由与多样性

目录 AI发展下的伦理挑战,应当如何应对? 方向一:构建可靠的AI隐私保护机制 方向二:确保AI算法的公正性和透明度 方向三:管控深度伪造技术 AI发展下的伦理挑战,应当如何应对? 在人工智能&…

Tita的OKR:高端制造行业的OKR案例

高端设备制造行业的发展趋势: 产业规模持续扩大:在高技术制造业方面,航空、航天器及设备制造业、电子工业专用设备制造等保持较快增长。新能源汽车保持产销双增,新材料新产品生产也高速增长。 标志性装备不断突破:例如…

数据结构第27节 优先队列

优先队列(Priority Queue)是在计算机科学中一种非常有用的抽象数据类型,它与标准队列的主要区别在于元素的出队顺序不是先进先出(FIFO),而是基于每个元素的优先级。具有较高优先级的元素会比低优先级的元素…

论文写作经验-摘要1

小王搬运工 时序课堂 2024年07月15日 13:10 新疆 本人菜鸡一名,最近几篇论文实验跑的比较顺利,结果也很不错,奈何于自己写作能力巨差,导致文章屡屡被拒。当前正在跟一位非常牛的老师学习写作技巧,我将一些心得体会和技…

MySQL教程 | 笔记 (包含数据库、表设计,数据库的增删改查操作;数据库优化等知识点)

SQL简介 一门操作关系型数据库的编程语言,定义操作所有关系型数据库的统一标准 通用语法: 可以单行或者多行书写,以分号结尾; 可以使用空格 / SQL语句可以使用空格/缩进来增强语句的可读性。 MySQL数据库的SOL语句不区分大小…

Flink Window 窗口【更新中】

Flink Window 窗口 在Flink流式计算中,最重要的转换就是窗口转换Window,在DataStream转换图中,可以发现处处都可以对DataStream进行窗口Window计算。 窗口(window)就是从 Streaming 到 Batch 的一个桥梁。窗口将无界流…

C#+GDAL影像处理笔记09:创建多边形、多部件图形、合并相邻的多边形

使用GDAL创建多边形、多部件要素、相邻面合并、以及shape文件创建的完整过程 1. 创建一个多边形 多边形必须闭合 // 创建第一个多边形几何对象Geometry polygon1 = new Geometry(wkbGeometryType.wkbPolygon);Geometry ring1 = new Geometry(wkbGeometryType.wkbLinearRing);…

银河麒麟如何部署QtMqtt(入门案例教程)

QtMqtt是一个基于Qt的MQTT客户端库,提供了使用MQTT协议与 MQTT broker 进行通信的功能。silver-linix是一个基于Linux的操作系统,用于嵌入式系统和物联网设备。下面将教您如何在silver-linix上部署QtMqtt。 1. 安装QtMqtt 1.1 安装QtMqtt依赖项 QtMqtt依赖于Qt和QtNetwork…

跨越空间的编码:在PyCharm中高效使用远程解释器

跨越空间的编码:在PyCharm中高效使用远程解释器 PyCharm的强大功能之一是支持远程解释器,这使得开发者能够在远程服务器或虚拟机上运行、调试代码,享受本地开发环境的便利。本文将详细介绍如何在PyCharm中配置和使用远程解释器,包…

系统架构师考点--软件工程(上)

大家好。今天我来总结一下软件工程的相关考点。这部分是考试的重点。在上午场客观题、下午场案例题以及下午场论文都有可能考到,在上午场客观题中大约占12-15分左右。 一、软件工程概述 软件开发生命周期 软件定义时期:包括可行性研究和详细需求分析过…

构建革新:在Gradle中打造自定义插件的终极指南

构建革新:在Gradle中打造自定义插件的终极指南 在构建自动化的征途上,Gradle以其灵活性和强大的扩展能力成为开发者的得力助手。自定义插件是Gradle生态中的一颗璀璨明珠,它允许开发者根据特定需求扩展Gradle的功能。本文将带你走进Gradle自…

uniapp打包成Android时,使用uni.chooseLocation在App端显示的地址列表是空白?一直转圈的解决办法

问题描述: uniapp打包后的测试版app在ios里可以显示高德地图的定位列表,但是安卓手机却不显示定位列表,一直在转圈圈,怎么回事?之前的功能在正式版都能用,真机运行也能用,为什么测试版的安卓手…

ADC性能规格--动态性能

所有真正的ADC都有额外的噪声源和失真过程,会降低性能。 动态性能规范报告了ADC动态行为中的这些缺陷,包括总谐波失真(THD)、信噪比加失真(SND)、信号噪声比(SNR)和无杂散动态范围&a…