singleton设计模式_Java Singleton设计模式

singleton设计模式

它是Java中最简单的设计模式之一。

如果有人问我哪种设计模式好,那么我会很自豪地说Singleton。

但是,当他们深入询问单身人士的概念时,我感到很困惑。

真的单身是那么困难吗?

确实不是,但是它有许多场景需要我们理解(尤其是初学者)。

定义:

在所有情况下,该类仅应允许一个实例,并且我们应提供对该实例的全局访问点。

定义就像1,2,3和A,B,C,D一样简单。

让我们看看如何实现Singleton类。

我们如何确保对象始终都是一个?

提示:将对象创建逻辑仅放在一个位置,并且不允许用户每次尝试都执行此逻辑,而只允许执行一次。

对象创建逻辑->它是什么
我们如何用Java创建对象?

是的,使用builder,我们不应该允许用户每次尝试访问构造器并执行它。
但是我们应该这样做一次,至少要得到一个对象。

那么,如何确保构造函数仅可访问和可执行一次?

  1. 防止在类外部访问构造函数,以使任何外部人员都无法创建实例。
    如何使它->如何防止类外部的方法访问?
    简单,将make方法作为私有权限,类似地,将构造函数作为私有权限。
  2. 防止构造函数在类中多次执行。
    如何制作->这有多种实现方式,下面以示例来看。

如果满足以上两个条件,则我们班级将始终有一个对象。 该类称为Singleton,因为它在我们请求的所有时间都产生单个对象。

没有太多理论,我们现在将开始实施。

创建单例对象的方法有很多:

方法1

  • 急于初始化或在使用前初始化
package com.kb.singleton;public class EagerSingletonClass {private static volatile EagerSingletonClass singletonInstance = new EagerSingletonClass();//making constructor as private to prevent access to outsidersprivate EagerSingletonClass() {}public static EagerSingletonClass getInstance(){return singletonInstance;}}

EagerSingletonClass的实例在类启动时创建。 由于它是静态的,因此在加载EagerSingletonClass时会对其进行加载和创建。

  • Junit测试类为上述类的单例测试。
package com.kb.singleton;import static org.junit.Assert.*;import org.junit.Test;public class EagerSingletonClassTest {@Testpublic void testSingleton() {EagerSingletonClass instance1 = EagerSingletonClass.getInstance();EagerSingletonClass instance2 = EagerSingletonClass.getInstance();System.out.println("checking singleton objects equality");assertEquals(true, instance1==instance2);}}

优势:
此策略在加载类期间创建对象,因此从多线程方案中可以更快更安全。 我们只需要使实例具有可变性即可处理多线程方案。

坏处 :

这种策略会在类加载本身时创建实例,因此,如果我们不使用它,那么这会浪费整个时间和内存来创建实例。 因此,最好在需要时选择一种策略来创建实例。

什么时候使用以上策略?
只要我们100%确定在我们的应用程序中肯定使用了该对象。
要么 当物体不重时,我们可以管理速度和内存。

方法2

  • 延迟初始化或在需要时初始化

与其在启动时创建对象,不如在需要时创建对象,这是很好的。 因此,让我们看看如何做到这一点:

package com.kb.singleton;public class LazySingleton {private static volatile LazySingleton singletonInstance = null;//making constructor as private to prevent access to outsidersprivate LazySingleton() {}public static LazySingleton getInstance(){if(singletonInstance==null){synchronized (LazySingleton.class) {singletonInstance = new LazySingleton();}}return singletonInstance;}}

在以上程序中,仅当通过getInstance()方法发出请求时,我们才创建了一个对象。

在此,在首次调用getInstance()的过程中,对象“ singletonInstance”将为null,并在条件变为true时执行if条件块并创建一个对象。

然后,对getInstance()方法的后续调用将返回相同的object。

但是,如果我们看一下多线程方案,问题就出在以下上下文中:2个线程t1和t2调用getInstance()方法,线程t1执行if(singletonInstance == null)并发现singletonInstance为null,因此它进入同步块以创建一个目的。

但是在执行对象创建逻辑之前,如果线程t2执行if(singletonInstance == null),那么它还将发现singletonInstance为null,因此它还将尝试输入同步块,但不会像第一个线程t1那样具有锁。

因此,线程t2等待线程t1完成同步块的执行。

因此线程t1执行并创建对象。 现在线程t2也正在等待同步块时进入同步块,并再次创建对象。

因此,两个线程创建了两个对象。 因此无法实现单例。

解决上述问题的方法是“ 双重检查锁定”。

它说在我们在同步块内执行对象创建的逻辑之前,请重新检查同步块内的实例变量。

这样一来,我们可以避免多个线程多次创建对象。

怎么样 ?

线程t1检查条件if(singletonInstance == null),第一次为真,因此它进入同步块,然后再次检查条件if(singletonInstance == null),也为真,因此创建了对象。

现在线程t2进入方法getInstance()并假定它在线程t1执行对象创建逻辑之前已执行if(singletonInstance == null)条件,然后t2也等待进入同步块。

在线程t1从同步块中出来之后,线程t2进入了同一块,但是我们再次在其中有if条件if(singletonInstance == null)但线程t1已经创建了一个对象,它使条件变为false并进一步停止执行并返回相同的实例。

让我们看看如何在代码中完成它:

package com.kb.singleton;public class LazySingletonDoubleLockCheck {private static volatile LazySingletonDoubleLockCheck singletonInstance = null;//making constructor as private to prevent access to outsidersprivate LazySingletonDoubleLockCheck() {}public static LazySingletonDoubleLockCheck getInstance(){if(singletonInstance==null){synchronized (LazySingleton.class) {if(singletonInstance ==null){singletonInstance = new LazySingletonDoubleLockCheck();}}}return singletonInstance;}}

让我们做单元测试

package com.kb.singleton;import static org.junit.Assert.*;import org.junit.Test;public class LazySingletonDoubleLockCheckTest {@Testpublic void testSingleton() {LazySingletonDoubleLockCheck instance1 = LazySingletonDoubleLockCheck.getInstance();LazySingletonDoubleLockCheck instance2 = LazySingletonDoubleLockCheck.getInstance();System.out.println("checking singleton objects equality");assertEquals(true, instance1==instance2);//fail("Not yet implemented");}}

上面的实现是针对单例模式的最佳建议解决方案,它最适合于单线程,多线程等所有情况。

方法3

  • 使用内部类的单例

让我们看一下下面的使用内部类创建对象的代码:

package com.kb.singleton;public class SingletonUsingInnerClass {private SingletonUsingInnerClass() {}private static class LazySingleton{private static final SingletonUsingInnerClass  SINGLETONINSTANCE = new SingletonUsingInnerClass();}public static SingletonUsingInnerClass getInstance(){return LazySingleton.SINGLETONINSTANCE;}}

单元测试代码

package com.kb.singleton;import static org.junit.Assert.*;import org.junit.Test;public class SingletonUsingInnerClassTest {@Testpublic void testSingleton() {SingletonUsingInnerClass instance1 = SingletonUsingInnerClass.getInstance();SingletonUsingInnerClass instance2 = SingletonUsingInnerClass.getInstance();System.out.println("checking singleton objects equality");assertEquals(true, instance1==instance2);}}

以上使用内部类创建对象的方法是创建单例对象的最佳方法之一。

在这里,除非并且直到有人尝试访问LazySingleton静态内部类的静态引用变量,否则将不会创建该对象。

因此,这还将确保在需要时以及在需要时创建对象。 而且实现起来非常简单。 从多线程进行也是安全的。

方法4

  • 具有序列化和反序列化的Singleton

现在假设我们的应用程序是分布式的,我们序列化我们的单例对象并将其写入文件。 稍后我们通过取消序列化单例对象来阅读它。 取消序列化对象始终会创建一个状态为文件内部可用的新对象。 如果在写入文件后进行任何状态更改,然后尝试取消序列化对象,则将获得原始对象,而不是新的状态对象。 因此,我们在此过程中得到了2个对象。

让我们尝试通过程序来了解这个问题:

第一件事->使singleton类可序列化以序列化和反序列化此类的对象。
第二件事->将对象写入文件(序列化)
第三件事->更改对象状态 第四件事-> de序列化对象

我们的单例课程如下:

package com.kb.singleton;import java.io.Serializable;public class SingletonSerializeAndDesrialize implements Serializable {private int x=100;private static volatile SingletonSerializeAndDesrialize singletonInstance = new SingletonSerializeAndDesrialize();private SingletonSerializeAndDesrialize() {}public static SingletonSerializeAndDesrialize getInstance() {return singletonInstance;}public int getX() {return x;}public void setX(int x) {this.x = x;}}

序列化我们的对象,然后对状态进行一些更改,然后取消序列化。

package com.kb.singleton;import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectInput;
import java.io.ObjectInputStream;
import java.io.ObjectOutput;
import java.io.ObjectOutputStream;public class SerializeAndDeserializeTest {static SingletonSerializeAndDesrialize instanceOne = SingletonSerializeAndDesrialize.getInstance();public static void main(String[] args) {try {// Serialize to a fileObjectOutput out = new ObjectOutputStream(new FileOutputStream("filename.ser"));out.writeObject(instanceOne);out.close();instanceOne.setX(200);// Serialize to a fileObjectInput in = new ObjectInputStream(new FileInputStream("filename.ser"));SingletonSerializeAndDesrialize instanceTwo = (SingletonSerializeAndDesrialize) in.readObject();in.close();System.out.println(instanceOne.getX());System.out.println(instanceTwo.getX());} catch (IOException e) {e.printStackTrace();} catch (ClassNotFoundException e) {e.printStackTrace();}}}

输出:

200

100

它清楚地表明,即使单身,我们也有2个不同的对象。 之所以发生这种情况,是因为反序列化会使用文件中可用状态创建新实例。

如何克服这个问题? 意味着如何防止在反序列化期间创建新实例?

解决方案非常简单–在您的singleton类中实现以下方法:

Access_modifier  Object readResolve() throws ObjectStreamException{
}

例:

Public Object readResolve() throws ObjectStreamException{
return modifiedInstance;
}

将其应用于上面的单例课程,则完整的单例课程如下:

package com.kb.singleton;import java.io.ObjectStreamException;
import java.io.Serializable;public class SingletonSerializeAndDesrialize implements Serializable {private int x=100;private static volatile SingletonSerializeAndDesrialize singletonInstance = new SingletonSerializeAndDesrialize();private SingletonSerializeAndDesrialize() {System.out.println("inside constructor");}public static SingletonSerializeAndDesrialize getInstance() {return singletonInstance;}public int getX() {return x;}public void setX(int x) {this.x = x;}public Object readResolve() throws ObjectStreamException{return singletonInstance;}}

现在运行上面的序列化和反序列化类,以检查两个实例的输出。

输出:

200

200

这是因为,在反序列化过程中,它将调用readResolve()方法,并且我们将返回现有实例,这将阻止创建新实例并确保单例对象。

  • 小心序列号

在序列化之后和取消序列化之前,只要类结构发生更改。 然后,在反序列化过程中,它将发现一个不兼容的类,并因此引发异常:java.io.InvalidClassException:SingletonClass; 本地类不兼容:流classdesc serialVersionUID = 5026910492258526905,本地类serialVersionUID = 3597984220566440782

因此,为避免发生此异常,我们必须始终对可序列化的类使用序列号ID。 其语法如下:

private static final long serialVersionUID = 1L;

因此,最后通过涵盖以上所有情况,单例类的最佳解决方案如下,我建议始终使用此解决方案:

package com.kb.singleton;import java.io.Serializable;public class FinalSingleton implements Serializable{private static final long serialVersionUID = 1L;private FinalSingleton() {}private static class LazyLoadFinalSingleton{private static final FinalSingleton  SINGLETONINSTANCE = new FinalSingleton();}public static FinalSingleton getInstance(){return LazyLoadFinalSingleton.SINGLETONINSTANCE;}private Object readResolve() {return getInstance();}}

翻译自: https://www.javacodegeeks.com/2014/05/java-singleton-design-pattern.html

singleton设计模式

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

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

相关文章

HD-SDI转HDMI转换器工作原理及功能介绍

高清HD-SDI转HDMI转换器是一款专用于单路高清数字分量串行接口(HD-SDI和3G-SDI)信号转换为HDMI信号的设备。该设备集成SDI接收器及HDMI调制器,可方便的将HD-SDI信号转为HDMI信号,同时会将SDI所带音频信号分离转换后嵌入到HDMI信号中,以实现声…

注释处理和JPMS

TLDR; 代替annotation.getClass().getMethod("value")调用annotation.annotationType().getMethod("value") 。 所有Java开发人员都听说过注释。 自Java 1.5(或者您坚持认为只有1.6)以来,我们便有了注释。 根…

HD-SDI转HDMI转换器怎么连接?hd-sdi转hdmi转换器常见故障有哪些?

高清HD-SDI转HDMI转换器是一款专用于单路高清数字分量串行接口(HD-SDI和3G-SDI)信号转换为HDMI信号的设备。hd-sdi转hdmi转换器将广播级输出图像使用的SDI信号转换成HDMI影像信号,中继广播与消费型产品的链接器,让SDI信号格式不用经剪辑软件再一次的转换…

sdi转hdmi转换器应用领域及规格参数详解

SDI转HDMI转换器 是一款专用于单路高清数字分量串行接口(HD-SDI,3G-SDI和SD-SDI)信号转换为HDMI 信号的设备。该设备集成SDI 接收器及HDMI 调制器,可方便的将SDI 信号转为HDMI 信号,同时会将SDI所带音频信号分离转换后嵌入到HDMI 信号中&…

Cassandra中的数据建模

在关系数据模型中,我们为域中的每个对象建模关系/表。 对于Cassandra,情况并非如此。本文将详细介绍在Cassandra中进行数据建模时需要考虑的所有方面。 以下是Cassandra数据建模的粗略概述。 从上图可以看出, 概念数据建模和应用程序查询是构…

485串口光纤转换器产品介绍

光纤转换器是RS-232/422/485串行数据通过光纤的远距离传输,可以完成串口到光纤的转换,并且可以延长串行通信信号的传输距离。接下来我们就跟随飞畅科技的小编一起来详细了解下光纤转换器的转换类别有哪些?一起来看看吧! 光纤转换器…

什么是485转光纤?485光纤转换器功能特点及技术参数详解

RS485转光纤转换器实现一路RS-485在光纤上的透明传输,由于采用光纤通信,解决了电磁干扰、地环干扰和雷电破坏的难题,大大提高了数据通讯的可靠性、安全性和保密性,可广泛用于各种工业控制、过程控制、交通控制和分布式数据采集等场…

可视对讲网络协议转换器怎么使用,协议转换器使用方法详细介绍

对于可视对讲网络协议转换器使用方法这块,可能很多人对此都不是很理解,在这里,我们首先来了解下可视对讲的原理,以便大家更好的了解接下来的内容。今天,杭州飞畅的小编就来为大家详细介绍下可视对讲网络协议转换器的使…

什么是协议转换器?协议转换器的定义

现如今,随着各种类型的转换器的出现,在很大程度上扩大了各类仪表的使用范围,使的自动控制系统具有更多的灵活性和更广的适应性,在这方面,协议转换器的应用范围也是非常的广泛。今天,杭州飞畅的小编就来为大…

什么是网络协议转换器?

通过之前的介绍,我们了解到协议转换是一种映射,就是把某一协议的收发信息(或事件)序列映射为另一协议的收发信息序列。那么,什么是网络协议转换器呢?接下来就跟随飞畅科技的小编一起来看看吧! 网络协议转换器是什么&a…

midlet_如何在J2ME中创建MIDlet

midlet总览 Java移动应用程序称为J2ME。 通常,当我们在移动技术领域工作时,我们必须考虑J2ME应用程序。 通过这种方式,我们可以开发我们的移动应用程序,也可以通过jad或jar文件将其安装在我们的设备中。 近年来,手机开…

网关到底是什么?协议转换器是网关吗?

网关(Gateway)又称网间连接器、协议转换器。网关在网络层以上实现网络互连,是最复杂的网络互连设备,仅用于两个高层协议不同的网络互连。网关既可以用于广域网互连,也可以用于局域网互连。那么,网关到底是什么呢?接下来…

穿越JUnit流

关于JUnit 5迁移的好处之一是,您可以在老式模式下运行JUnit 4测试,并且所有内容仍然兼容。 不利的一面是,某些注释和方法在JUnit 4和JUnit 5中具有相同的名称,并且当两组库依赖项都可用时,很容易导入错误的内容并产生不…

什么是无线路由器网络协议?

上一篇我们介绍了什么是网络协议转换器,相信看过的朋友对此都有了一定的认知,可能有些朋友在使用协议转换器的时候用的是无线路由器网络,那么,什么是无线路由器网络协议呢?接下来飞畅科技的小编就来为大家详细介绍下无…

协议转换器安全使用须知

协议转换器能使处于通信网上采用不同高层协议的主机仍然互相合作,完成各种分布式应用。可以将IEEE802.3协议的以太网或V.35数据接口同标准G.703协议的2M接口之间进行相互转换。也可以在232/485/422串口和E1、CAN接口及2M接口进行转换。那么,我们在使用协…

JDBC –模拟序列

也许我们每个人在程序员的生命中至少遇到过一次这个问题- 如何模拟数据库序列? 在下面,您可以找到我对这个问题的解决方案的变形。 假设我们有一个接口定义了所需的API,用于返回整数序列: public interface Sequences { int next…

怎么安装协议转换器?协议转换器安装方法解析

协议转换器是有很多种的,多数基本上是个2层设备,经常碰见的一种RAD的协议转换器是将2M的E1线路转换成V.35的数据线路连接路由器的设备,当然也有,2M转双绞线以太的,借助2M通信线路可以实现局域网范围的远程接入和扩大。…

关于协议转换器的分类以及工作原理的详细介绍

现如今,随着互联网的广泛应用,我们国内的网民也是突破了8.29亿,相信,大家对于网络这块是非常的熟悉了,它是一种虚拟的东西,但是它几乎存在于我们生活的各个角落,在很大程度的让我们的日常生活变…

ElasticSearch-Hadoop:从Hadoop到ElasticSearch索引产品视图计数和客户顶部搜索查询

这篇文章涵盖了如何使用ElasticSearch-Hadoop从Hadoop系统读取数据并在ElasticSearch中对其进行索引。 它涵盖的功能是在最近n天中为每个客户的产品浏览量计数和热门搜索查询编制索引。 可以在网站上进一步使用分析后的数据来显示最近浏览过的客户,产品浏览次数和热…

协议转换器是怎么分类的?主要有哪些类别?

工业通信采用的通信接口各不相同,需要多个设备之间的信息共享和数据交换,而常用的工控设备通信口有RS-232、RS-485、CAN和网络,由于各种通信结构的协议不兼容,使得异构网络之间的操作和信息交换难以进行,通过多协议转换…