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)

