Java Singleton设计模式最佳实践与示例
Java Singleton Pattern是四种帮派设计模式之一,属于创建设计模式类别。从定义来看,它似乎是一个非常简单的设计模式,但是当涉及到实现时,它会带来很多实现问题。Java Singleton模式的实现一直是开发人员争议的话题。在这里,我们将了解Singleton设计模式原理,实现Singleton设计模式的不同方法以及一些使用它们的最佳实践。
Java Singleton
- Singleton模式限制类的实例化,并确保java虚拟机中只存在该类的一个实例。
- 单例类必须提供一个全局访问点来获取类的实例。
- 单例模式用于日志记录,驱动程序对象,缓存和线程池。
- Singleton设计模式也用于其他设计模式,如Abstract Factory,Builder,Prototype,Facade等。
- 例如
java.lang.Runtime
,单核设计模式也用于核心java类中java.awt.Desktop
。
Java Singleton模式
为了实现Singleton模式,我们有不同的方法,但它们都有以下常见概念。
- 私有构造函数,用于限制其他类的实例化。
- 同一个类的私有静态变量,它是该类的唯一实例。
- 返回类实例的公共静态方法,这是外部世界获取单例类实例的全局访问点。
在后面的部分中,我们将学习Singleton模式实现的不同方法以及设计实现的关注点。
急切的初始化
在急切的初始化中,Singleton类的实例是在类加载时创建的,这是创建单例类的最简单方法,但它有一个缺点,即即使客户端应用程序可能没有使用它,也会创建实例。
这是静态初始化单例类的实现。
package com.journaldev.singleton;
public class EagerInitializedSingleton {
private static final EagerInitializedSingleton instance = new EagerInitializedSingleton();
//private constructor to avoid client applications to use constructor
private EagerInitializedSingleton(){}
public static EagerInitializedSingleton getInstance(){
return instance;
}
}
如果您的单例类没有使用大量资源,那么这就是使用方法。但是在大多数场景中,Singleton类是为文件系统,数据库连接等资源创建的。除非客户端调用该getInstance
方法,否则我们应该避免实例化。此外,此方法不提供任何异常处理选项。
静态块初始化
静态块初始化实现类似于急切初始化,除了在静态块中创建类的实例,该静态块提供异常处理选项。
package com.journaldev.singleton;
public class StaticBlockSingleton {
private static StaticBlockSingleton instance;
private StaticBlockSingleton(){}
//static block initialization for exception handling
static{
try{
instance = new StaticBlockSingleton();
}catch(Exception e){
throw new RuntimeException("Exception occured in creating singleton instance");
}
}
public static StaticBlockSingleton getInstance(){
return instance;
}
}
急切初始化和静态块初始化都会在使用之前创建实例,这不是最佳实践。因此,在后面的部分中,我们将学习如何创建支持延迟初始化的Singleton类。
阅读:Java静态
延迟初始化
实现Singleton模式的延迟初始化方法在全局访问方法中创建实例。以下是使用此方法创建Singleton类的示例代码。
package com.journaldev.singleton;
public class LazyInitializedSingleton {
private static LazyInitializedSingleton instance;
private LazyInitializedSingleton(){}
public static LazyInitializedSingleton getInstance(){
if(instance == null){
instance = new LazyInitializedSingleton();
}
return instance;
}
}
上述实现在单线程环境中可以正常工作,但是当涉及到多线程系统时,如果多个线程同时位于if条件内,则会导致问题。它将破坏单例模式,两个线程将获得单例类的不同实例。在下一节中,我们将看到创建线程安全单例类的不同方法。
线程安全单例
创建线程安全的单例类的更简单方法是使全局访问方法同步,以便一次只有一个线程可以执行此方法。这种方法的一般实现类似于下面的类。
package com.journaldev.singleton;
public class ThreadSafeSingleton {
private static ThreadSafeSingleton instance;
private ThreadSafeSingleton(){}
public static synchronized ThreadSafeSingleton getInstance(){
if(instance == null){
instance = new ThreadSafeSingleton();
}
return instance;
}
}
上面的实现工作正常并提供了线程安全性,但由于与synchronized方法相关的成本,它降低了性能,尽管我们只需要可能创建单独实例的前几个线程(读取:Java同步)。为了避免每次额外的开销,使用双重检查锁定原理。在此方法中,synchronized块在if条件内使用,并进行额外检查以确保仅创建单个类的一个实例。
下面的代码片段提供了双重检查的锁定实现。
public static ThreadSafeSingleton getInstanceUsingDoubleLocking(){
if(instance == null){
synchronized (ThreadSafeSingleton.class) {
if(instance == null){
instance = new ThreadSafeSingleton();
}
}
}
return instance;
}
阅读:线程安全单例类
Bill Pugh Singleton实施
在Java 5之前,java内存模型存在很多问题,并且上述方法在某些情况下失败,在这些情况下,太多线程试图同时获取Singleton类的实例。因此Bill Pugh提出了一种使用内部静态助手类创建Singleton类的不同方法。Bill Pugh Singleton的实施就是这样的;
package com.journaldev.singleton;
public class BillPughSingleton {
private BillPughSingleton(){}
private static class SingletonHelper{
private static final BillPughSingleton INSTANCE = new BillPughSingleton();
}
public static BillPughSingleton getInstance(){
return SingletonHelper.INSTANCE;
}
}
请注意包含singleton类实例的私有内部静态类。加载单例类时,SingletonHelper
类不会加载到内存中,只有当有人调用getInstance方法时,才会加载此类并创建Singleton类实例。
这是Singleton类最广泛使用的方法,因为它不需要同步。我在我的许多项目中使用这种方法,它也很容易理解和实现。
阅读:Java嵌套类
使用Reflection来销毁Singleton Pattern
反射可用于销毁所有上述单例实现方法。让我们看一下示例类。
package com.journaldev.singleton;
import java.lang.reflect.Constructor;
public class ReflectionSingletonTest {
public static void main(String[] args) {
EagerInitializedSingleton instanceOne = EagerInitializedSingleton.getInstance();
EagerInitializedSingleton instanceTwo = null;
try {
Constructor[] constructors = EagerInitializedSingleton.class.getDeclaredConstructors();
for (Constructor constructor : constructors) {
//Below code will destroy the singleton pattern
constructor.setAccessible(true);
instanceTwo = (EagerInitializedSingleton) constructor.newInstance();
break;
}
} catch (Exception e) {
e.printStackTrace();
}
System.out.println(instanceOne.hashCode());
System.out.println(instanceTwo.hashCode());
}
}
当您运行上面的测试类时,您会注意到两个实例的hashCode与销毁单例模式的方法不同。反射是非常强大的,并且在很多框架中使用,比如Spring和Hibernate,请查看Java Reflection Tutorial。
Enum Singleton
为了克服这种情况,Joshua Bloch建议使用Enum来实现Singleton设计模式,因为Java确保任何枚举值只在Java程序中实例化一次。由于Java Enum值是全局可访问的,因此单例也是如此。缺点是枚举类型有些不灵活; 例如,它不允许延迟初始化。
package com.journaldev.singleton;
public enum EnumSingleton {
INSTANCE;
public static void doSomething(){
//do something
}
}
阅读:Java Enum
序列化和单身人士
有时在分布式系统中,我们需要在Singleton类中实现Serializable接口,以便我们可以将其状态存储在文件系统中并在以后的时间点检索它。这是一个小型单例类,它也实现了Serializable接口。
package com.journaldev.singleton;
import java.io.Serializable;
public class SerializedSingleton implements Serializable{
private static final long serialVersionUID = -7604766932017737115L;
private SerializedSingleton(){}
private static class SingletonHelper{
private static final SerializedSingleton instance = new SerializedSingleton();
}
public static SerializedSingleton getInstance(){
return SingletonHelper.instance;
}
}
序列化单例类的问题在于,每当我们反序列化它时,它将创建该类的新实例。让我们用一个简单的程序来看。
package com.journaldev.singleton;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
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 SingletonSerializedTest {
public static void main(String[] args) throws FileNotFoundException, IOException, ClassNotFoundException {
SerializedSingleton instanceOne = SerializedSingleton.getInstance();
ObjectOutput out = new ObjectOutputStream(new FileOutputStream(
"filename.ser"));
out.writeObject(instanceOne);
out.close();
//deserailize from file to object
ObjectInput in = new ObjectInputStream(new FileInputStream(
"filename.ser"));
SerializedSingleton instanceTwo = (SerializedSingleton) in.readObject();
in.close();
System.out.println("instanceOne hashCode="+instanceOne.hashCode());
System.out.println("instanceTwo hashCode="+instanceTwo.hashCode());
}
}
上述程序的输出是;
instanceOne hashCode=2011117821
instanceTwo hashCode=109647522
所以它破坏了单例模式,为了克服这种情况我们需要做的就是提供readResolve()
方法的实现。
protected Object readResolve() {
return getInstance();
}
在此之后,您会注意到两个实例的hashCode在测试程序中是相同的。
我希望这篇文章可以帮助您掌握Singleton设计模式的精细细节,通过您的想法和评论让我知道。
转载来源:https://www.journaldev.com/1377/java-singleton-design-pattern-best-practices-examples