java的单例设计模式
在本教程中,我们将看到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)