当您只想拥有给定类的一个实例时,将使用单例设计模式。它是一种创造性的设计模式,我们处理对象的创建。
动机和现实世界的例子
在面向对象的设计中,某些类只有一个实例非常重要。那是因为它们代表了一种独特的东西,这是同类产品中的一种。
让我们从Java语言中看到一些真实世界的Singletons示例,以了解这意味着什么 -
java.lang.Runtime:Java提供了一个Runtime类,它表示运行应用程序的当前运行时环境。应用程序可以使用此类与其运行时环境进行交互。由于Runtime环境是唯一的,因此该类只应该有一个实例。java.awt.Desktop:Desktop该类允许Java应用程序使用在本机桌面上注册的应用程序(如用户的默认浏览器或邮件客户端)启动URI或文件。本机桌面和相关应用程序是独一无二的。所以必须只有一个Desktop类的实例。单例设计模式的实现
你如何确保一个类只有一个实例?好吧,在Java中有几种方法可以做到这一点。但所有这些都基于以下基本思想:
声明一个私有构造函数以防止其他人实例化该类。在静态字段/块中的类加载期间创建类的实例,或者在静态方法中按需创建,该静态方法首先检查实例是否存在,并且仅在不存在的情况下创建新实例。让我们逐个查看代码示例的所有可能解决方案:
1.初始化恶汉单例设计模式
这是最简单的方法,其中类的实例是在类加载时创建的 -
这种方法的缺点是无论是否访问实例都会创建实例。如果对象很简单并且不包含任何系统资源,那么这很好。但是,如果它分配大量系统资源并且仍未使用,则可能会对性能产生影响。
2.初始化静态恶汉单例设计模式
您还可以在静态块中创建类的一次性实例。这是有效的,因为静态块仅在类加载时执行一次。
静态块初始化的优点是您可以编写初始化逻辑或处理静态块中的异常。
与前面的解决方案一样,无论应用程序是否需要,都会创建实例。
3.初始化懒汉单例设计模式
延迟初始化意味着延迟某些事情的初始化,直到第一次需要它为止。
在以下实现中,我们首先检查该getInstance()方法中是否已创建实例。如果实例已经创建,我们只需返回它,否则,我们首先创建实例然后返回它:
注意synchronized在getInstance()方法中使用关键字。这是防止多线程环境中的竞争条件所必需的。
假设instance尚未创建,两个线程getInstance()同时进入该方法。在这种情况下,instance==null检查将评估为true,并且两个线程都将创建该类的新实例。synchronized关键字可确保只有一个线程可以执行getInstance()在一个时间的方法。
4.初始化懒汉双重锁定单例模式
synchronized添加到getInstance()方法的关键字可以防止竞争条件,但也会导致一些性能损失。
以下是lazily初始化单例的优化版本 - synchronized我们创建一个synchronized块并仅包装synchronized块内的实例化部分- 而不是创建整个方法-
上面的方法称为Double-Checked Locking,因为我们仔细检查变量是否在synchronized块内初始化。
仔细检查在这里非常重要。让我们说两个线程T1并同时T2输入getInstance()方法。该instance==null检查将评估为true,因此他们都将进入synchronized块一个接一个。如果不存在双重检查,则两个线程都将创建一个新实例。
另外,请注意volatile关键字与实例变量的使用。这对于防止编译器进行自己的优化并正确处理单例是必要的。
百度百科对双重检查锁定以及Java代码有很好的解释。
5.初始化懒汉内部单例模式
请注意,getInstance()在第一次调用该方法之前,不会加载内部类。此解决方案是线程安全的,不需要任何同步。它是所有单例设计模式实现中最有效的方法。
6.枚举单例模式
Enum是单身设计。所有枚举值仅在类加载时初始化一次。
这种方法的缺点是与其他方法相比,它有点不灵活。
单例模式和反射
Java的Reflection API非常强大。即使类的构造函数是私有的,您也可以使用Reflection实例化一个类。
让我们看看它的实际效果:
请注意我们如何使用创建Singleton的新实例constructor.newInstance()。这破坏了单例设计模式。
防止反射
为了保护你的单例类不受反射实例化的影响,你可以从私有构造函数中抛出异常,如果实例已经像这样创建了 -
您还可以使用Enum单例来防止反射。无法通过反射初始化枚举。无论如何,它们都是单一实例的可靠方法。
单例模式和序列化
我们经常需要在Java中序列化/反序列化对象。任何需要序列化/反序列化的类都必须实现可序列化的接口。
请注意,反序列化步骤始终会创建类的新实例,从而破坏单例模式。这是一个例子 -
注意原始实例和反序列化实例的hashCodes是如何不同的。我们的单例模式课程显然有两个例子。
防止序列化
要防止反序列化过程创建新实例,可以readResolve()在单例类中实现该方法。在反序列化对象时调用它。
在该readResolve()方法中,您必须返回现有实例 -
结论
在本文中,您了解了什么是单例设计模式以及何时应该使用它。您学习了实现单例设计模式的各种方法,并了解了每种方法的优缺点。