单例设计模式–内省和最佳实践

定义:

Singleton是“ 四人帮”设计模式的一部分,它属于创新设计模式。 在本文中,我们将更深入地研究Singleton模式的用法。 就建模而言,它是最简单的设计模式之一,但另一方面,就使用的复杂性而言,这也是最有争议的模式之一。
在Java中,Singleton模式将确保在Java虚拟机中仅创建一个类的实例。 它用于提供对对象的全局访问点。 在实际使用中,Singleton模式用于日志记录,缓存,线程池,配置设置,设备驱动程序对象。

设计模式通常与工厂设计模式结合使用。 此模式也用于Service Locator JEE模式。

结构体:

单例类图

单例类图

  • 静态成员:包含单例类的实例。
  • 私有构造函数:这将阻止其他人实例化Singleton类。
  • 静态公共方法:此方法提供对Singleton对象的全局访问点,并将实例返回给客户端调用类。

实现示例:延迟初始化

让我们看一下Java中的单例实现示例。 下面的代码使用惰性初始化过程。

public class SingletonExample {// Static member holds only one instance of the// SingletonExample classprivate static SingletonExample singletonInstance;// SingletonExample prevents any other class from instantiatingprivate SingletonExample() {}// Providing Global point of accesspublic static SingletonExample getSingletonInstance() {if (null == singletonInstance) {singletonInstance = new SingletonExample();}return singletonInstance;}public void printSingleton(){System.out.println('Inside print Singleton');}
}
单例模式代码说明

单例模式代码说明

当使用SingletonExample.getSingletonInstance()。printSingleton()从客户端调用此类时。 那么在第一次时只会创建一个实例。 在第二次以后的所有后续调用中,我们将引用相同的对象,并且getSingletonInstance()方法返回在第一次创建时所用的SingletonExample类的相同实例。 您可以通过添加以下代码作为打印语句来测试:

public static SingletonExample getSingletonInstance() {if (null == singletonInstance) {singletonInstance = new SingletonExample();System.out.println('Creating new instance');}return singletonInstance;}

如果现在我们从客户端类中将Singleton类称为:

SingletonExample.getSingletonInstance().printSingleton();
SingletonExample.getSingletonInstance().printSingleton();
SingletonExample.getSingletonInstance().printSingleton();
SingletonExample.getSingletonInstance().printSingleton();

上面的调用的输出是:

Creating new instance
Inside print Singleton
Inside print Singleton
Inside print Singleton
Inside print Singleton

实施示例:双重检查锁定

上面的代码在单线程环境中绝对可以正常工作,并且由于延迟初始化而可以更快地处理结果。 但是,以上代码可能会在多线程环境中的结果中产生一些突然的行为,因为在这种情况下,如果多个线程尝试同时访问getSingletonInstance()方法,则它们可能会创建同一SingletonExample类的多个实例。 想象一个实际的情况,我们必须创建一个日志文件并对其进行更新,或者在使用诸如打印机之类的共享资源时进行更新。 为避免这种情况,我们必须使用某种锁定机制,以便第二个线程在第一个线程完成该过程之前不能使用此getInstance()方法。

堆中的单例

堆中的单例

在图3中,我们显示了多个线程如何访问单例实例。 由于单例实例是存储在堆的PermGen空间中的静态类变量。 这也适用于getSingletonInstance()实例方法,因为它也是静态的。 在多线程环境中,为了防止每个线程创建单例对象的另一个实例并因此导致并发问题,我们将需要使用锁定机制。 这可以通过synced关键字实现。 通过使用这个synced关键字,我们可以防止Thread2或Thread3在getSingletonInstance()方法内部的Thread1时访问单例实例。

从代码角度来看,这意味着:

public static synchronized SingletonExample getSingletonInstance() {if (null == singletonInstance) {singletonInstance = new SingletonExample();}return singletonInstance;}

因此,这意味着每次调用getSingletonInstance()都会给我们带来额外的开销。 为了避免这种昂贵的操作,我们将使用双重检查锁定,以便仅在第一次调用期间进行同步,并且将这种昂贵的操作限制为仅发生一次。 仅在以下情况才需要:

singletonInstance = new SingletonExample();

代码示例:

public static volatile SingletonExample getSingletonInstance() {if (null == singletonInstance) {synchronized (SingletonExample.class){if (null == singletonInstance) {singletonInstance = new SingletonExample();}}}return singletonInstance;}

在上面的代码片段中,假设有多个线程并发并尝试创建新实例。 在这种情况下,可能有三个或更多线程在同步块上等待访问。 由于我们使用了同步,因此只有一个线程可以访问。 当第一个线程退出该块时,将等待所有在同步块上等待的其余线程。 但是,当剩余的并发线程进入同步块时,由于双重检查:空检查,它们被阻止进一步进入。 由于第一个线程已经创建了一个实例,因此没有其他线程会进入此循环。

其余所有不幸运地与第一个线程一起进入同步块的线程将在第一个null检查时被阻塞。 这种机制称为双重检查锁定 ,它提供了显着的性能优势,并且是具有成本效益的解决方案。

实施示例:易失性关键字

我们还可以在实例变量声明中使用volatile关键字。

private volatile static SingletonExample singletonInstance;

volatile关键字在多线程环境中用作并发控制工具,并以最准确的方式提供最新更新。但是请注意,双重检查锁定可能在Java 5之前不起作用。在这种情况下,我们可以使用早期加载机制。 如果我们还记得原始的示例代码,则使用了延迟加载。 如果提早加载,我们将在开始时实例化SingletonExample类,并将其引用到私有静态实例字段。

public class SingletonExample {private static final SingletonExample singletonInstance = new SingletonExample;// SingletonExample prevents any other class from instantiatingprivate SingletonExample() {}// Providing Global point of accesspublic static SingletonExample getSingletonInstance() {return singletonInstance;}public void printSingleton(){System.out.println('Inside print Singleton');}
}

在这种方法中,单例对象是在需要之前创建的。 JVM负责静态变量的初始化,并确保进程是线程安全的,并确保在线程尝试访问它之前创建实例。 在延迟加载的情况下,当客户端类调用getSingleInstance()而在早期加载的情况下,是singletonInstance创建当类在存储器中加载的singletonInstance被创建。

实现示例:使用枚举

使用Enum在Java 5或更高版本中实现Singleton:
Enum是线程安全的,并且通过Enum实现Singleton可以确保即使在多线程环境中,Singleton也只有一个实例。 让我们看一个简单的实现:

public enum SingletonEnum {INSTANCE;public void doStuff(){System.out.println('Singleton using Enum');}
}
And this can be called from clients :
public static void main(String[] args) {SingletonEnum.INSTANCE.doStuff();}

常见问题解答:

问题:为什么我们不能使用静态类而不是单例呢?
回答:

  • 与静态类相比,单例的主要优点之一是它可以实现接口并扩展类,而静态类则不能(它可以扩展类,但不继承其实例成员)。 如果我们考虑静态类,那么它只能是嵌套的静态类,因为顶级类不能是静态类。 静态意味着它属于它所在的类,而不属于任何实例。 因此,它不能是顶级课程。
  • 另一个区别是,静态类仅与Singleton不同,其所有成员都将是静态的。
  • Singleton的另一个优点是它可以延迟加载,而static每次首次加载时都会被初始化。
  • 单例对象存储在堆中,而静态对象存储在堆栈中。
  • 我们可以克隆Singleton的对象,但是不能克隆静态类对象。
  • Singleton可以使用多态的面向对象功能,但是静态类不能。

问题:单例类可以被子类化吗?
答:坦率地说,单例只是一种设计模式,可以将其子类化。 但是,值得理解子类继承子类的逻辑或要求,因为子类可能不会通过扩展Singleton类来继承单例模式目标。 但是,可以通过在类声明中使用final关键字来防止子类化。

问题:使用克隆是否可以有多个单例实例?
答:很好! 我们现在干什么? 为了防止创建单例实例的另一个实例,我们可以从clone()方法内部引发异常。

问题:如果我们使用序列化和反序列化创建另一个单例实例,会产生什么影响?
答:当我们序列化一个类并反序列化它时,它将创建单例类的另一个实例。 基本上,您对单例实例进行反序列化的次数将创建多个实例。 在这种情况下,最好的方法是将单例设为枚举。 这样,底层的Java实现即可处理所有细节。 如果这不可能,那么我们将需要重写readobject()方法以返回相同的单例实例。

问题:Singleton可以使用哪种其他模式?
答:还有其他一些模式,例如Factory方法,构建器和原型模式,它们在实现过程中使用Singleton模式。

问题:JDK中的哪些类使用单例模式?
答案:java.lang.Runtime:在每个Java应用程序中,只有一个Runtime实例,允许该应用程序与其运行的环境进行交互。 getRuntime等效于单例类的getInstance()方法。

Singleton设计模式的用途:

单例模式的各种用法:

  • 硬件接口访问:单例的使用取决于要求。 但是,在需要外部硬件资源使用限制的情况下,实际上可以使用单例,例如,可以将打印后台处理程序做成单例的硬件打印机,以避免多次并发访问并产生死锁。
  • 记录器:同样,单例是在日志文件生成中使用的一个很好的潜在候选者。 想象一个应用程序,其中日志记录实用程序必须根据从用户收到的消息来生成一个日志文件。 如果存在多个使用此日志记录实用程序类的客户端应用程序,则它们可能会创建此类的多个实例,并且在并发访问同一记录器文件期间可能会引起问题。 我们可以将logger实用程序类用作单例,并提供全局参考。
  • 配置文件:这是单例模式的另一个潜在候选者,因为它具有性能优势,因为它可以防止多个用户重复访问和读取配置文件或属性文件。 它创建配置文件的单个实例,该实例可被多个调用同时访问,因为它将提供加载到内存对象中的静态配置数据。 该应用程序仅在第一次读取配置文件,然后从第二次调用开始,客户端应用程序从内存对象中读取数据。
  • 缓存:我们可以将缓存用作单例对象,因为它可以具有全局参考点,并且对于将来对缓存对象的所有调用,客户端应用程序都将使用内存中对象。

动手:

让我们以一个小的实际示例来更详细地了解Singleton设计模式。
问题陈述:
设计一个小型的ATM打印应用程序,它可以生成多种类型的交易对帐单,包括迷你对帐单,明细对帐单等。但是,客户应注意这些对帐单的创建。 确保将内存消耗降至最低。 设计方案: 可以使用四个设计模式的两个核心Gang来满足以上要求-工厂设计模式和Singleton设计模式。 为了在ATM机中为ATM事务生成多种类型的语句,我们可以创建一个Statement Factory对象,该对象具有工厂方法createStatements() 。 createStatement将创建DetailedStatement或MiniStatement对象。 客户端对象将完全不知道对象的创建,因为它仅与Factory接口交互。 我们还将创建一个名为StatementType的接口。 这将允许添加其他对帐单类型对象,例如信用卡对帐单等。 因此,该解决方案遵循面向对象的“打开/关闭”设计原则,具有可扩展性和可扩展性。 减少内存消耗的第二个要求可以通过使用Singleton设计模式来实现。 不需要多次启动Statement Factory类,并且一个工厂可以创建多个语句对象。 单例模式将创建StatementFactory类的单个实例,从而节省内存。

ATM范例

ATM范例

  • 工厂:工厂是一个抽象类,是客户的单点联系。 所有具体的工厂类都需要实现抽象工厂方法。
  • StatementFactory:这是Factory创建类,由creator方法组成。 该类从Factory抽象类扩展。这是所有产品(例如Statements)的主要创建者类。
  • StatementType:这是一个产品接口,它提供对需要由Factory类创建的各种类型的产品的抽象。
  • DetailedStatement:这是StatementType接口的具体实现,它将打印详细的语句。
  • MiniStatement:这是StatementType接口的具体实现,它将打印迷你语句。
  • 客户端:这是客户端类,它将调用StatementFactory和StatementType并请求创建对象。

假设:
该设计解决方案仅适用于一台ATM机。

样例代码:

工厂.java

public abstract class Factory {protected abstract StatementType createStatements(String selection);}

StatementFactory.java

public class StatementFactory extends Factory {private static StatementFactory uniqueInstance;private StatementFactory() {}public static StatementFactory getUniqueInstance() {if (uniqueInstance == null) {uniqueInstance = new StatementFactory();System.out.println('Creating a new StatementFactory instance');}return uniqueInstance;}public StatementType createStatements(String selection) {if (selection.equalsIgnoreCase('detailedStmt')) {return new DetailedStatement();} else if (selection.equalsIgnoreCase('miniStmt')) {return new MiniStatement();}throw new IllegalArgumentException('Selection doesnot exist');}
}

StatementType.java

public interface StatementType {String print();
}

DetailedStatement.java

public class DetailedStatement implements StatementType {@Overridepublic String print() {System.out.println('Detailed Statement Created');return 'detailedStmt';}
}

MiniStatement.java

public class MiniStatement implements StatementType {@Overridepublic String print() {System.out.println('Mini Statement Created');return 'miniStmt';}
}

客户端程序

public class Client {public static void main(String[] args) {System.out.println('Enter your selection:');BufferedReader br = new BufferedReader(new InputStreamReader(System.in));String selection = null;try {selection = br.readLine();} catch (IOException e) {e.printStackTrace();}Factory factory = StatementFactory.getUniqueInstance();StatementType objStmtType = factory.createStatements(selection);System.out.println(objStmtType.print());}}

结论:

在以上文章中,我们详细介绍了Singleton模式,如何在实际应用中实现Singleton模式。 尽管单例模式看起来很简单,但除非有强烈要求,否则我们应该阻止自己使用它。 您可以将结果归咎于多线程环境中结果的不可预测性。 尽管我们可以在Java 5及更高版本中使用枚举 ,但有时很难始终在枚举中实现您的逻辑,否则Java 5之前可能会有遗留代码。希望我们的读者喜欢本文。

参考: Singleton设计模式–来自ICG博客上我们JCG合作伙伴 Mainak Goswami 的内省和最佳实践 。

翻译自: https://www.javacodegeeks.com/2013/02/singleton-design-pattern-an-introspection-and-best-practices.html

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

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

相关文章

修改用户名_Word办公技巧:如何更改文档修订者的用户名?

在一份文档需要多人参与修订的时候,每个审阅者将用户名改为自己的名字就可以方便地看出具体哪个参与者做了哪些改动。下面我们来学习一下更改修订者用户名的办法。 应用场景 下图文档中默认修订者用户名为"AutoBVT",接下来我们想将其改为"李小白"。 操作…

Java伪随机数生成器(PRNG)中的弱点

这将是Kai Michaelis,JrgSchwenk和我撰写的论文的总结,该论文在RSA Conference 2013的密码学家会议上发表 。 你可以得到我的演讲的幻灯片在这里和我们的全文在这里 。 我们对PRNG(主要是SecureRandom)附带的常见Java库生成的随机…

vue安装概要以及vue测试工具

一、概述 1、安装node,去node官网 2、新建一个项目,通过npm init命令初始化,即创建一个package.json文件 3、用命令 npm install vue -g 全局安装vue(网上已经有详细的资料,在此就不复述了) 二、vue测试…

linux安装与远程连接

1、在VMware上创建新的虚拟机,保证有linux版本的iso镜像文件 具体参考http://jingyan.baidu.com/article/eae0782787b4c01fec548535.html 2、进行切换用户 进行root用户的切换:   #su password: 进行普通用户的切换: #su username 3、编辑…

Spring框架:三种Spring Bean生命周期技术

当使用术语“生命周期”时,Spring的家伙指的是您的bean的构造和破坏,通常这与Spring Context的构造和破坏有关。 在某些情况下,Bean生命周期的管理不是一件容易的事,因为需要它执行自己的内部设置。 当您的bean必须与外部系统进行…

wincc客户机打开服务器文件通讯不上,wincc客户机连不上服务器

如果你计算机名称改正确了,检查一下安装OPC服务软件吗?比如SIMATIC NET。然后试一试下面方法能否解决:首先,设置一下PG/PC接口。方法:开始--设置--控制面板--设置PG/PC接口,在接口分配参数,选择你要的参数(根据你的情…

spring boot高性能实现二维码扫码登录(上)——单服务器版

前言 目前网页的主流登录方式是通过手机扫码二维码登录。我看了网上很多关于扫码登录博客后,发现基本思路大致是:打开网页,生成uuid,然后长连接请求后端并等待登录认证相应结果,而后端每个几百毫秒会循环查询数据库或r…

查看 固态硬盘位置_3米防摔+人脸/指纹解锁:西数Armorlock移动固态硬盘

要求快速而又安全的数据拷贝工具?指纹识别移动SSD大家应该都见过了,今天西数推出了一个更为特别的人脸/指纹识别加密移动SSD。G-Technology Armorlock使用AES256全盘加密固态硬盘中的数据,解锁方式不是常见的密码或自带指纹传感器&#xff0c…

web前端工程师全套教程免费分享

这是我自己早前听课时整理的前端全套知识点,适用于初学者,也可以适用于中级的程序员,你们可以下载下来。我自认为还是比较系统全面的,可以抵得上市场上90%的学习资料。讨厌那些随便乱写的资料还有拿出来卖钱的人!在这里…

mybatis一对一联表查询的两种常见方式

1.一条语句执行查询(代码如下图) 注释:class表(c别名),teacher表(t别名)teacher_id为class表的字段t_id为teacher表的字段,因为两者有主键关联的原因,c_id为c…

在Windows 7中设置Java开发环境

一段时间以来,我收到了很多愿意尝试Java语言的学生和人们的要求,它们提供了关于如何设置Java开发环境的简单指南,类似于我一年前写的那样。 Mac用户。 看到这里和这里 。 因此,本文主要针对Java开发新手,他们寻求有关使…

写给想成为前端工程师的同学们―前端工程师是做什么的?

前端工程师是做什么的? 前端工程师是互联网时代软件产品研发中不可缺少的一种专业研发角色。从狭义上讲,前端工程师使用 HTML、CSS、JavaScript 等专业技能和工具将产品UI设计稿实现成网站产品,涵盖用户PC端、移动端网页,处理视觉…

逆水寒服务器维护7.5,逆水寒7.26日维护到什么时候 逆水寒7.26日游戏改动汇总介绍...

逆水寒7.26日维护到什么时候 逆水寒7.26日游戏改动汇总介绍2018-07-26 10:08:08来源:游戏下载编辑:苦力趴评论(0)《逆水寒》官方发布微博,称为了保证服务器的运行稳定和服务质量,将于7月26日上午7:00-上午10:00进行停服维护。此次…

是否可以限制蓝牙传输距离_技术文章—关于蓝牙传输范围的常见误解

蓝牙技术在耳机、手机、手表及汽车领域的普及为人们带来了许多便利,却也引发了一些人们对于蓝牙的误解。目前,蓝牙可为多种重要的解决方案提供支持,其中包括家庭自动化、室内导航以及商业和工业创新等。误解一:蓝牙稳定传输的最远…

基于webpack搭建的vue element-ui框架

花了1天多的时间, 终于把这个框架搭建起来了。 好了, 不多说了, 直接进入主题了。前提是安装了nodejs,至于怎么安装, 网上都有教程。 这里就不多说了, 这边使用的IDE是idea。1.在E:/my-project(我的电脑上&…

编译打包vue_Vue 源码分析( 一 )

Vue 源码分析( 一 )目录结构、版本、入口1、Vue 源码目录结构dist:打包之后生成的结果目录 examples:代码示例 scripts:配置文件 src:源代码目录compiler: 编译相关 (将template模板转换成rende…

使用grep4j轻松测试分布式组件上的SLA

因此,您的分布式体系结构如下图所示,您刚刚从企业那里收到了一项要求,以确保生产者发送并随后传输到下游系统(消费者)的消息的SLA必须快且永远不会慢于此。 400毫秒。 要求说: 从生产者发送到任何消费者的…

Python+Appium环境搭建

1、python环境搭建,这里就不做过多介绍 2、安装 node.js 2.1、官网下载node.js:https://nodejs.org/en/download/ 2.2、获取到安装文件后,直接双击安装文件,根据程序的提示,完成nodejs的安装。 2.3、安装完成后&#x…

以空格为分隔符读取内容给两个变量_问与答61: 如何将一个文本文件中满足指定条件的内容筛选到另一个文本文件中?...

学习Excel技术,关注微信公众号:excelperfectQ:如下图1所示,一个名为“InputFile.csv”文件,每行有6个数字,每个数字使用空格分隔开。图1现在,我要将以60至69开头的行放置到另一个名为“OutputFi…

BZOJ 1008:[HNOI2008]越狱

傻逼题&#xff0c;然后n&#xff0c;m写反了WA了一发。。 #include<cstdio> #include<cstring> #include<iostream> #include<algorithm> #include<cmath> #include<queue> #include<vector> typedef long long LL; using namespac…