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设计模式

singleton设计模式_Java Singleton设计模式相关推荐

  1. singleton设计模式_Java Singleton设计模式最佳实践与示例

    singleton设计模式 Java Singleton Pattern is one of the Gangs of Four Design patterns and comes in the Cr ...

  2. java api中的设计模式_Java API 设计模式之策略(Strategy)

    策略设计模式与状态设计模式相类似.我们提到过,状态设计模式包含一个状态对象,该对象封装一个上下文对象的状态.策略设计模式包含一个策略对象.该对象与状态设计模式的状态对象相类似.它们之间的关键不同在于: ...

  3. java.io设计模式_JAVA IO 设计模式彻底分析

    无论是哪种编程语言,输入跟输出都是重要的一部分,Java也不例外,而且Java将输入/输出的功能和使用范畴做了很大的扩充.它采用了流的机制来实现输入/输出,所谓流,就是数据的有序排列,而流可以是从某个 ...

  4. java线程设计模式_JAVA多线程设计模式

    漫谈UML UML 类图 类和层次结构的关系 接口与实现 聚合 访问控制 类间的关联性 顺序图 处理流程和对象间的协调 时序图 Introduction 1 Java语言的线程 Java语言的线程 何 ...

  5. 常见设计模式—单例模式(Singleton)

    前言 好久没写东西了,但是想着无论什么事还是要坚持自己初心要坚持的东西.写东西不能断! 对于常用的23种设计模式,这里笔者会根据自己学习和出现频率.重要程度进行学习记录吧.并且每种设计模式可能会根据暂 ...

  6. Android设计模式——单例模式(Singleton)

    二十三种设计模式分为三大类: 创建型模式,共五种:工厂方法模式.抽象工厂模式.单例模式.建造者模式.原型模式. 结构型模式,共七种:适配器模式.装饰器模式.代理模式.外观模式.桥接模式.组合模式.享元 ...

  7. C++设计模式--单例模式(Singleton)及单例通用模板

    概述 C++中的单例模式应该是设计模式中最简单的了,在编码中常见到.那么,简单的总结下 C++中的单例模式写法,以及根据单例模式扩展后的一些写法,最后还有单例的通用模板,可用于快捷创建一个单例类. 单 ...

  8. C#面向对象设计模式纵横谈——Singleton单件(创建型模式)

    Singleton单件(创建型模式) 动机(Motivation) 在软件系统中,经常有这样一些特殊的类,必须保证它们在系统中只存在一个实例,才能确保它们的逻辑正确性.以及良好的效率. 如何绕过常规的 ...

  9. 设计模式(2)--Singleton(单例模式)--创建型

    1.模式定义: 单例模式确保一个类只有一个实例,而且自行实例化并向整个系统提供这个实例. 2.模式特点: (1)单例类只能有一个实例. (2)单例类必须自己创建自己的唯一实例. (3)单例类必须给所有 ...

最新文章

  1. CAD绘图软件中如何查询图纸的版本是多少
  2. C语言实现radon变换
  3. 坚持学习打卡的人,将来会变成什么样?
  4. 推荐一款Python开源库,技术人必备的造数据神器!
  5. LeetCode 756. 金字塔转换矩阵(回溯)
  6. SDN,你必须了解的基础知识【定期更新】
  7. 使用web3和infura开发以太坊ethereum区块链
  8. pcre的compile,exec和free的代码
  9. mongoDB在centos7上的安装
  10. python对象和类_Python面向对象(一)类与对象
  11. 使用 VBScript 判断是否安装了雅虎助手的方法
  12. 什么是火星坐标系(GCJ-02)
  13. TIM1_ETR和TIM1_CH有什么差别,要进行输入捕获
  14. c# wifi串口通信_C#串口通信 SerialPort类
  15. 三星Galaxy Note 10.1刷机教程
  16. 刷题之旅第35站,CTF show 萌新题目集合
  17. MapReduce发生Permission denied: user=zhen, access=WRITE错误
  18. hp服务器pe系统安装win7系统安装系统安装失败,使用硬盘安装系统出现pGptRestore部署失败怎么解决?...
  19. Joan Baez - Jackaroe
  20. e5 2680 v性能服务器,E5 V2处理器性能全面测试_浪潮服务器_服务器评测与技术-中关村在线...

热门文章

  1. hdu4699-Editor【对顶栈】
  2. P2814-家谱【图论,并查集,std map库】
  3. 【状压DP】滚榜(P7519)
  4. codeforces F.F. Teodor is not a liar! 最长不降子序列
  5. Hadoop入门(十七)Mapreduce的多表关联程序
  6. 跟我学 Java 8 新特性之 Stream 流(五)映射
  7. Java开发必须掌握的5种加密策略
  8. Maven精选系列--私库搭建及使用
  9. 一张图弄懂java线程的状态和生命周期
  10. 家的味道,家的感觉!!!