java的单例设计模式

时间:2020-02-23 14:35:35  来源:igfitidea点击:

在本教程中,我们将看到Java中的Singleton设计模式。

单例设计模式是最简单的设计模式之一,但单例特性可以使用多线程,反射,序列化等打破,因此在创建单例类时需要小心。

Singleton设计模式限制了一个类来创建多个对象,因此我们只能创建单级级别的一个实例.Singleton类用于日志记录,数据库连接,缓存和线程池.ETC。

单例类

有很多方法可以创建单例,但它有很少的常见步骤。

  • 使构造函数私有:我们需要使构造函数私有,以便无法从课堂外部创建类的实例。
  • 提供静态方法来访问单例类的对象:创建静态方法,该方法将返回单例类的对象。
  • 创建与公共静态相同类的实例,静态方法将返回。

有很多方法可以创建单例级,我们将详细看看。

马上初始化(Eager initialization)

马上初始化是创建SINGLETON.Object的最简单方法之一,该类创建了一类加载到内存中。
它是通过在声明引用变量时创建类的对象来完成的。

让我们通过示例来理解:

package org.igi.theitroad.singleton;
 
public class EagerInitializationSingleton {
	
	private static final EagerInitializationSingleton instance = new EagerInitializationSingleton();
    
	private EagerInitializationSingleton()
	{
		//private constructor
	}
	
	public static EagerInitializationSingleton getInstance()
	{
		return instance;
	}
}

即使客户端不需要Singleton对象,仍然是创建的。
如果单例对象不是资源密集,我们仍然可以使用它,但通常,当我们使用单例进行连接池或者数据库连接时,它是资源密集的。

延迟初始化

延迟初始化是创建单例的另一种方式。
在变量声明时创建对象的原始方法,我们可以在静态方法GetInstance中创建它,如下所示

package org.igi.theitroad.singleton;
 
public class LazyInitializationSingleton {
 
	private static LazyInitializationSingleton instance;
 
	private LazyInitializationSingleton()
	{
		//private constructor
	}
 
	public static LazyInitializationSingleton getInstance()
	{
		if(instance==null)
		{
			instance= new LazyInitializationSingleton();
		}
		return instance;
	}
}

上面代码将在单线程系统的情况下正常工作,但在多线程的情况下,我们可能最终创建多个类实例。

线程安全单例

创建上方的最简单方法是线程安全的方法是使GetInstance方法单例如下。

package org.igi.theitroad.singleton;
 
public class ThreadSafeSingleton {
 
	private static ThreadSafeSingleton instance;
 
	private ThreadSafeSingleton()
	{
		//private constructor
	}
 
	public static synchronized ThreadSafeSingleton getInstance()
	{
		if(instance==null)
		{
			instance= new ThreadSafeSingleton();
		}
		return instance;
	}
}

上面的方法将正常工作并提供线程安全,但是由于我们使整个方法同步,因此有与之相关的性能成本。
我们可以实施双重检查锁定模式而不是制作GetInstance方法。

双重检查锁定

双重检查锁定使用同步块,带有另外的null检查,以确保仅为单例类创建一个实例,如下所示。

package org.igi.theitroad.singleton;
 
public class ThreadSafeSingletonDoubleCheck {
 
	private static volatile ThreadSafeSingletonDoubleCheck instance;
 
	private ThreadSafeSingletonDoubleCheck()
	{
		//private constructor
	}
 
	public static synchronized ThreadSafeSingletonDoubleCheck getInstance()
	{
		if(instance==null)
		{
			synchronized (ThreadSafeSingletonDoubleCheck.class) {
				if(instance==null)
				{	
					instance= new ThreadSafeSingletonDoubleCheck();
				}
			}
			
		}
		return instance;
	}
}

为什么你需要双重检查锁定?
代码以下可能有问题。

public static synchronized ThreadSafeSingletonDoubleCheck getInstance()
	{
		if(instance==null)
		{
			synchronized (ThreadSafeSingletonDoubleCheck.class) {
				
				instance= new ThreadSafeSingletonDoubleCheck();
			}
			
		}
		return instance;
	}

让我们说出一个发现实例的线程t1和t2,因为null.both t1和t2等待类级锁.t1锁定并创建threadsafesingletondublebeck的实例并释放锁定。
T2现在锁定锁定,它也是创造了单例的对象,违反了我们的要求,这就是我们在这里需要双重检查的原因。

Bill Pugh Singleton实施

Bill Pugh Singleton实现是创建单例级的最佳方式之一。
它利用静态内部类来创建Operon类。

package org.igi.theitroad.singleton;
 
public class BillPughSingleton {
	
	private BillPughSingleton()
	{
		//private constructor
	}
	
	 private static class SingletonHelper{
	        private static final BillPughSingleton INSTANCE = new BillPughSingleton();
	    }
	    
	    public static BillPughSingleton getInstance(){
	        return SingletonHelper.INSTANCE;
	    }
}

加载单例类时,未加载内部类,因此在加载类时不会创建单例类的对象。
仅在调用getInstance()方法时才创建内部类。
所以它起初看起来像急切初始化,但它实际上是延迟初始化。

使用枚举创建单例

由于Java枚举是全局常量,只加载一次。

Joshua Bloch建议枚举是单例设计模式的最佳候选人。
请注意,Java枚举不允许延迟初始化,因此即使我们不使用单例,仍将加载。
Enum Exmaple:

package org.igi.theitroad.singleton;
 
public enum SingletonEnum {
	INSTANCE;
	
	//Write other methods
}

由于枚举不提供任何显式构造函数,即使使用反射也无法创建多个Sinlgeton类的实例。

用反射测试单例

我们可以轻松地销毁单例,以帮助所有以上实现Excpet enum.reflection是实例化对象的强大机制。

让我们通过示例来理解:

package org.igi.theitroad.singleton;
import java.lang.reflect.Constructor;
 
public class ReflectionSingletonTest {
 
    public static void main(String[] args) {
    	BillPughSingleton instance1 = BillPughSingleton.getInstance();
    	BillPughSingleton instance2 = null;
        try {
            Constructor[] constructors = BillPughSingleton.class.getDeclaredConstructors();
            for (Constructor constructor : constructors) {
                //Below code will destroy the singleton pattern
                constructor.setAccessible(true);
                instance2 = (BillPughSingleton) constructor.newInstance();
                break;
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
        System.out.println("Hashcode of instance1: "+instance1.hashCode());
        System.out.println("Hashcode of instance2: "+instance2.hashCode());
    }
 
}

运行上面的程序时,我们将得到以下输出:

Hashcode of instance1: 865113938
Hashcode of instance2: 1442407170

如我们所见,instance1和instance2的哈希码是不同的,因此我们能够创建两个单例类实例。

当反射尝试将对象的实例第二次按照下面创建对象的实例时,可以通过抛出运行时异常来限制方案。

package org.igi.theitroad.singleton;
 
public class BillPughSingleton {
	
	private BillPughSingleton()
	{
		//private constructor
		if(SingletonHelper.INSTANCE!=null)
		{
			throw new RuntimeException("You can not create object of singleton class twice");
		}
	}
	
	 private static class SingletonHelper{
	        private static final BillPughSingleton INSTANCE = new BillPughSingleton();
	    }
	    
	    public static BillPughSingleton getInstance(){
	        return SingletonHelper.INSTANCE;
	    }
 
}

当我们立即运行反射时,我们将得到以下输出。

java.lang.reflect.InvocationTargetException
at sun.reflect.NativeConstructorAccessorImpl.newInstance0(Native Method)
at sun.reflect.NativeConstructorAccessorImpl.newInstance(NativeConstructorAccessorImpl.java:62)
at sun.reflect.DelegatingConstructorAccessorImpl.newInstance(DelegatingConstructorAccessorImpl.java:45)
at java.lang.reflect.Constructor.newInstance(Constructor.java:423)
at org.igi.theitroad.singleton.ReflectionSingletonTest.main(ReflectionSingletonTest.java:14)
Caused by: java.lang.RuntimeException: You can not create object of singleton class twice
at org.igi.theitroad.singleton.BillPughSingleton.(BillPughSingleton.java:10)
... 5 more
Hashcode of instance1: 1028566121
Exception in thread “main" java.lang.NullPointerException
at org.igi.theitroad.singleton.ReflectionSingletonTest.main(ReflectionSingletonTest.java:21)

正如我们所看到的,上面的程序抛出运行时异常,当反射尝试以两次创建单例类的对象时。

用序列化测试单例

当我们序列化和反序列化Singleton类的对象时,我们应该得到相同的对象。
让我们看看我们是否会在简单的例子的帮助下或者不是相同的对象。
让我们首先使Billpughsingletlon类序列化。

package org.igi.theitroad.singleton;
 
import java.io.Serializable;
 
public class BillPughSingleton implements Serializable{
 
	private static final long serialVersionUID = 2088778914384963252L;
 
	private BillPughSingleton()
	{
		//private constructor
		if(SingletonHelper.INSTANCE!=null)
		{
			throw new RuntimeException("You can not create object of singleton class twice");
		}
	}
 
	private static class SingletonHelper{
		private static final BillPughSingleton INSTANCE = new BillPughSingleton();
	}
 
	public static BillPughSingleton getInstance(){
		return SingletonHelper.INSTANCE;
	}
 
}

让我们创建SingleterserializedTest以测试序列化方案。

package org.igi.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 {
        BillPughSingleton instance1 = BillPughSingleton.getInstance();
        //Serialize the object
        ObjectOutput out = new ObjectOutputStream(new FileOutputStream(
                "singleton.ser"));
        out.writeObject(instance1);
        out.close();
        
       //deserialize the object
        ObjectInput in = new ObjectInputStream(new FileInputStream(
                "singleton.ser"));
        BillPughSingleton instance2 = (BillPughSingleton) in.readObject();
        in.close();
        
        System.out.println("Hashcode of instance1: "+instance1.hashCode());
        System.out.println("Hashcode of instance2: "+instance2.hashCode());
    }
}

运行上面的程序时,我们将得到以下输出。

Hashcode of instance1: 589431969
Hashcode of instance2: 295530567

如我们所见,instance1和instance2的哈希码是不同的,因此我们能够在序列化的帮助下创建两个单例类实例。

实现ReadResolve方法以克服以下方案如下。

protected Object readResolve() {
    return getInstance();
}

当我们现在运行SingleterSerializedTest时,我们将得到以下输出。

Hashcode of instance1: 589431969
Hashcode of instance2: 589431969

用克隆测试单例。

如果单例类实现克隆接口,则需要重写(overwriting)克隆方法并从中抛出CloneNoteUpportedException。

public Object clone() throws CloneNotSupportedException
{
	return new CloneNotSupportedException("You can not clone object of Singleton class ");
}

让我们创建克隆最终创建克隆方案。

package org.igi.theitroad.singleton;
 
public class CloningSingleonTest {
 
	public static void main(String[] args) throws CloneNotSupportedException {
		BillPughSingleton instance1 = BillPughSingleton.getInstance();
		BillPughSingleton instance2=(BillPughSingleton) instance1.clone();
 
		System.out.println("Hashcode of instance1: "+instance1.hashCode());
		System.out.println("Hashcode of instance2: "+instance2.hashCode());
	}
 
}

运行上面的程序时,我们将得到以下输出。

Exception in thread “main" java.lang.ClassCastException: java.lang.CloneNotSupportedException cannot be cast to org.igi.theitroad.singleton.BillPughSingleton
at org.igi.theitroad.singleton.CloningSingleonTest.main(CloningSingleonTest.java:7)