Java Singleton设计模式最佳实践与示例
Java Singleton模式是"四个设计"模式的帮派之一,位于"创新设计模式"类别中。
从定义上看,这似乎是一个非常简单的设计模式,但是当涉及到实现时,它会带来很多实现方面的问题。
Java Singleton模式的实现一直是开发人员中有争议的话题。
其中我们将学习Singleton设计模式的原理,实现Singleton设计模式的不同方法以及使用它的一些最佳实践。
单例模式
单例模式限制了一个类的实例化,并确保在Java虚拟机中仅存在该类的一个实例。
单例类必须提供全局访问点才能获取该类的实例。
单例模式用于日志记录,驱动程序对象,缓存和线程池。
Singleton设计模式还用于其他设计模式,例如Abstract Factory,Builder,Prototype,Facade等。
Singleton设计模式也用于核心Java类,例如," java.lang.Runtime"," java.awt.Desktop"。
Java单例模式实现
要实现Singleton模式,我们有不同的方法,但是所有方法都具有以下共同概念。
私有构造函数,用于限制该类从其他类的实例化。
同一类的私有静态变量,是该类的唯一实例。
返回类实例的公共静态方法,这是外部世界获取单例类实例的全局访问点。
在进一步的章节中,我们将学习Singleton模式实现的不同方法以及与实现有关的设计问题。
在急切的初始化中,在加载类时会创建Singleton类的实例,这是创建Singleton类的最简单方法,但是它存在一个缺点,即使客户端应用程序可能不使用它也会创建该实例。
1.渴望初始化
这是静态初始化单例类的实现。
如果您的单例类没有使用大量资源,则可以使用这种方法。
但是在大多数情况下,都是为文件系统,数据库连接等资源创建Singleton类的。
除非客户端调用getInstance方法,否则应避免实例化。
另外,此方法不提供任何用于异常处理的选项。
package com.theitroad.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;
}
}
静态块初始化的实现与渴望的初始化类似,不同的是,类的实例是在提供了异常处理选项的静态块中创建的。
2.静态块初始化
急切的初始化和静态块初始化都在实例被使用之前就创建了实例,这不是最佳实践。
因此,在进一步的章节中,我们将学习如何创建支持延迟初始化的Singleton类。
package com.theitroad.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类的示例代码。
3.延迟初始化
上面的实现在单线程环境下可以很好地工作,但是对于多线程系统,如果多个线程同时位于if条件中,则可能导致问题。
它将破坏单例模式,并且两个线程都将获得单例类的不同实例。
在下一节中,我们将介绍创建线程安全的单例类的不同方法。
package com.theitroad.singleton;
public class LazyInitializedSingleton {
private static LazyInitializedSingleton instance;
private LazyInitializedSingleton(){}
public static LazyInitializedSingleton getInstance(){
if(instance == null){
instance = new LazyInitializedSingleton();
}
return instance;
}
}
创建线程安全的单例类的更简单方法是使全局访问方法同步,以便一次只能有一个线程执行此方法。
这种方法的一般实现类似于下面的类。
4.线程安全单例
上面的实现可以很好地工作并提供线程安全性,但是由于与同步方法相关的成本,它降低了性能,尽管我们仅对可能创建单独实例的前几个线程需要它(请参阅:Java同步)。
为了避免每次另外的开销,使用了双重检查的锁定原理。
在这种方法中,在if条件中使用了同步块,并进行了附加检查,以确保仅创建一个singleton类的实例。
package com.theitroad.singleton;
public class ThreadSafeSingleton {
private static ThreadSafeSingleton instance;
private ThreadSafeSingleton(){}
public static synchronized ThreadSafeSingleton getInstance(){
if(instance == null){
instance = new ThreadSafeSingleton();
}
return instance;
}
}
以下代码片段提供了双重检查的锁定实现。
阅读:线程安全单例类
public static ThreadSafeSingleton getInstanceUsingDoubleLocking(){
if(instance == null){
synchronized (ThreadSafeSingleton.class) {
if(instance == null){
instance = new ThreadSafeSingleton();
}
}
}
return instance;
}
在Java 5之前,Java内存模型存在很多问题,并且上述方法在某些情况下会失败,在某些情况下,太多的线程试图同时获取Singleton类的实例。
因此,比尔·普格(Bill Pugh)提出了另一种方法,以使用内部静态帮助程序类创建Singleton类。
Bill Pugh Singleton的实现是这样的;
5. Bill Pugh Singleton执行
请注意,内部私有静态类包含单例类的实例。
当加载singleton类时,SingletonHelper类不会加载到内存中,只有当有人调用getInstance方法时,该类才会加载并创建Singleton类实例。
package com.theitroad.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类使用最广泛的方法,因为它不需要同步。
我在许多项目中都使用了这种方法,而且也很容易理解和实施。
阅读:Java嵌套类
反射可用于销毁所有上述单例实现方法。
让我们用一个示例类来看看。
6.使用反射破坏单例模式
当您运行上述测试类时,您会注意到两个实例的hashCode不同,这会破坏单例模式。
反射功能非常强大,并在诸如Spring和Hibernate的许多框架中使用,请查看Java Reflection Tutorial。
package com.theitroad.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());
}
}
为了通过反射来克服这种情况,Joshua Bloch建议使用Enum来实现Singleton设计模式,因为Java确保在Java程序中仅将一次枚举值实例化一次。
由于Java枚举值可全局访问,因此单例也是如此。
缺点是枚举类型有些不灵活;例如,它不允许延迟初始化。
7.枚举辛格顿
package com.theitroad.singleton;
public enum EnumSingleton {
INSTANCE;
public static void doSomething(){
//do something
}
}
有时在分布式系统中,我们需要在Singleton类中实现Serializable接口,以便我们可以将其状态存储在文件系统中,并在以后的某个时间点检索它。
这是一个小的单例类,它也实现了Serializable接口。
8.序列化和单例
序列化单例类的问题在于,每当我们反序列化它时,它将创建该类的新实例。
让我们用一个简单的程序看看它。
package com.theitroad.singleton;
import java.io.Serializable;
public class SerializedSingleton implements Serializable{
private static final long serialVersionUID = -7604766932016737115L;
private SerializedSingleton(){}
private static class SingletonHelper{
private static final SerializedSingleton instance = new SerializedSingleton();
}
public static SerializedSingleton getInstance(){
return SingletonHelper.instance;
}
}
上面程序的输出是:
package com.theitroad.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());
}
}
因此,它破坏了单例模式,以克服这种情况,我们需要做的所有事情就是提供readResolve()方法的实现。
instanceOne hashCode=2011117821 instanceTwo hashCode=109647522
此后,您将注意到两个实例的hashCode在测试程序中相同。
protected Object readResolve() {
return getInstance();
}

