单例设计模式–鸟瞰

几天前,当我回到家乡时,我的一位来自同事的大三学生参加了一家跨国公司的采访,在采访过程中受了重伤。 我的意思是,由于面试小组提出的难题,他无法使面试合格。 当我回到班加罗尔时,他分享了他在技术面试中遇到的尴尬处境。 根据他今天的经验,我正在撰写有关Singleton设计模式的文章。 顺便说一下,我的下级同事在Java方面拥有近四年的经验。 他面临的一个有争议的问题是“ 什么是Singleton设计模式,您将如何编写健壮的Singleton类? ”但是,让我给您提供在项目/产品开发时经常使用的Singleton设计模式的基本和关键轮廓。
如您所知,Singleton设计模式属于“ Creational Pattern ”类别。 基本原则说,在任何时间点,一个类都应该只有一个实例,而与一个类的多次调用无关。 此原理背后有许多概念和设计。 许多开发人员采用不同的方式在Java中实现Singleton。 一些开发人员根本不喜欢这种设计。 但是,我们应该专注于这种设计,而其他因素对我们而言则完全不相关。 让我们从各种角度分析此设计。

技术性

正如我已经提到的,将有一个类的实例,让我们看下面的代码。

package com.ddlab.rnd.patterns;
public class SingletonType1 
{
	private static SingletonType1 instance = null;

	private SingletonType1()
	{
		super();
	}

	public static SingletonType1 getInstance()
	{
		if( instance == null )
			instance = new SingletonType1();
		return instance;
	}
}

要使用和实现,我们必须编写以下代码。

SingletonType1 instance = SingletonType1.getInstance();

很好,似乎是正确的。 如果您编写10次以上的代码,您将获得相同的实例。 检查以上程序的正确性。 让我们做一个基本的临床测试。 通过调用代码“ SingletonType1.getInstance()”来创建上述类的实例,并将所有实例放入Set中。 如您所知,Set不允许重复。 因此,最后,如果获得集合1的大小,则它是正确的实现。 您也可以。 肯定会得到结果为1,即Set的大小。 现在我们想到了一个问题,我们可以打破以上设计吗? 是否可以创建上述定义的类的多个实例? 是。 我们可以。 我们可以打破以上设计,并可以创建多个实例。 那个怎么样 ????????

让我们看下面的代码。

package com.ddlab.rnd.patterns;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.Method;

public class TestSingletonType1 
{
	public void createMultiInstances()
	{
		System.out.println("\n** MULTIPLE INSTANCES FROM SINGLETO **\n");
		/*
		 * Using Reflection you can break singleton
		 */
		try 
		{
			Class clazz = Class.forName("com.ddlab.rnd.patterns.SingletonType1");
			Constructor constructor = clazz.getDeclaredConstructors()[0];
			constructor.setAccessible(true);
			SingletonType1 instance1 = (SingletonType1)constructor.newInstance(null);
			SingletonType1 instance2 = (SingletonType1)constructor.newInstance(null);
			SingletonType1 instance3 = (SingletonType1)constructor.newInstance(null);

			System.out.printf( "%-15s %-15s %n", "SERIAL NO", "MULTI INSTANCES");
			System.out.printf( "%-15s %-15s %n", "---------", "---------------");

			System.out.format("%-15s %-15s %n", "INSTANCE 1 ",instance1);
			System.out.format("%-15s %-15s %n", "INSTANCE 2 ",instance2);
			System.out.format("%-15s %-15s %n", "INSTANCE 3 ",instance3);
		}
		catch (Exception e) 
		{
			e.printStackTrace();
		}
	}

	public void createMultiInstances1()
	{
		System.out.println("\n********* MULTIPLE INSTANCES FROM SINGLETON ********\n");
		/*
		 * Using Reflection you can break singleton
		 */
		try 
		{
			Class clazz = Class.forName("com.ddlab.rnd.patterns.SingletonType1");
			Method method = clazz.getDeclaredMethods()[0];
			Field field = clazz.getDeclaredFields()[0];
			field.setAccessible(true);

			SingletonType1 instance1 = (SingletonType1)method.invoke(clazz, null);
			field.set(clazz, null);
			SingletonType1 instance2 = (SingletonType1)method.invoke(clazz, null);
			field.set(clazz, null);
			SingletonType1 instance3 = (SingletonType1)method.invoke(clazz, null);

			System.out.printf( "%-15s %-15s %n", "SERIAL NO", "MULTI INSTANCES");
			System.out.printf( "%-15s %-15s %n", "---------", "---------------");

			System.out.format("%-15s %-15s %n", "INSTANCE 1 ",instance1);
			System.out.format("%-15s %-15s %n", "INSTANCE 2 ",instance2);
			System.out.format("%-15s %-15s %n", "INSTANCE 3 ",instance3);
		}
		catch (Exception e) 
		{
			e.printStackTrace();
		}
	}

	public void createInstances()
	{
		System.out.println("\n*********** SINGLE INSTANCES FROM SINGLETON ********\n");
		SingletonType1 instance1 = SingletonType1.getInstance();
		SingletonType1 instance2 = SingletonType1.getInstance();
		SingletonType1 instance3 = SingletonType1.getInstance();

		System.out.printf( "%-15s %-15s %n", "SERIAL NO", "INSTANCES");
		System.out.printf( "%-15s %-15s %n", "---------", "----------");

		System.out.format("%-15s %-15s %n", "INSTANCE 1 ",instance1);
		System.out.format("%-15s %-15s %n", "INSTANCE 2 ",instance2);
		System.out.format("%-15s %-15s %n", "INSTANCE 3 ",instance3);
	}

	public static void main(String[] args) 
	{
		new TestSingletonType1().createInstances();
		new TestSingletonType1().createMultiInstances();
		new TestSingletonType1().createMultiInstances1();
	}

}

如果运行上面的程序,您将能够看到已定义的singleton类的许多实例。

但是我们知道,可以使用反射来破坏Singleton的私有构造方法。 在上述情况下,我们可以创建具有私有构造函数的类的实例,也可以访问私有字段。 哦,是的。。。您真的创建了多个实例吗?是的,BOSS,我做了,您觉得呢? 您以任何方式构建设计,但我可能会破坏。 确实,这伤害了像我这样真正的情感开发者的情绪。 OKKkkk。 现在,我将编写一个非常有效的代码,这样您就不会崩溃。 真的是…….. ???????? 学习Java Relection机制对于探索JAVA的美丽至关重要。

现在让我们看看如何编写更好的代码,以便其他开发人员将无法使用反射来破坏代码。

package com.ddlab.rnd.patterns;
import java.lang.reflect.ReflectPermission;
import java.security.Permission;

public class SingletonType2 
{
	static
	{
		getInstance();
	}
	private static SingletonType2 instance = null;

	private SingletonType2()
	{
		super();
		//Add the following piece of code so that it can not be invoked using relection
		System.setSecurityManager(new SecurityManager() {
            @Override
            public void checkPermission(Permission perm) 
            {
            	if (perm instanceof ReflectPermission )
            	{
            		System.out.println("\nYes I will not allow you to create the instance using Reflection...\n");
            		throw new SecurityException();
            	}
            	else
            	{
            		//Do nothing
            	}
            }

        });
	}

	public static SingletonType2 getInstance()
	{
		if( instance == null )
			instance = new SingletonType2();

		return instance;
	}
}

现在确实如此,您的反射攻击将不会影响上述代码。 如果使用反射创建另一个实例,则将在此处获得Exception。 您可以考虑一下,使用Java自省实用程序可能会破坏它。 您可能还认为,我们将不会访问构造函数,而是将访问该字段,然后将字段值设置为null,然后再次调用该字段。 这是一个很好的策略,但是您会失败,因为自省实用程序是另一种反映。 由于我们不允许反射,因此您将无法创建多个实例。 但是,您仍然可以使用自省来调用方法“ getInstance()”,但是您将获得相同的实例。 因此,我们的反思和内省思想可以在这种情况下被抛弃。 让我们以不同的方式思考,指向类的序列化。 那么如果我们要序列化会发生什么呢? 在上面的类中,您不能进行继承,因为构造函数是私有的;对于防弹机制,您可以将类定为最终类。 我们无法序列化SingletonType2类,因为它没有实现Serializable接口,并且我们也不允许反射。 但是,我们无法序列化未实现Serilizable接口的类。 但是,有时需要将Singleton对象保留一天。 在这种情况下,我们必须在单例类中实现Serializable接口。 现在我们的项目或产品需要序列化,并且我们将不使用SecurityManager概念。 让我们修改上面的类。

让我们看看带有Seri​​alizable接口的Singleton类。

package com.ddlab.rnd.patterns;
import java.io.Serializable;

public class SingletonType11 implements Serializable
{
	private static final long serialVersionUID = -4137189065490862968L;
	private static SingletonType11 instance = null;

	private SingletonType11()
	{
		super();
	}

	public static SingletonType11 getInstance()
	{
		if( instance == null )
			instance = new SingletonType11();
		return instance;
	}
}

好的,我们将能够序列化上面的类,但是我们再次为黑客提供了创建多个实例的机会,因此我们的概念再次在这里中断。 让我们看看如何通过对象序列化再次打破Singleton的概念。 让我们用这样编写一个小类。

package com.ddlab.rnd.patterns;
import java.io.Serializable;

public class BreakSingleton implements Serializable 
{
	private static final long serialVersionUID = 5904306999023481976L;

	private SingletonType11 instance2 = SingletonType11.getInstance();

	public SingletonType11 getInstance2() {
		return instance2;
	}

	public void setInstance1(SingletonType11 instance2) {
		this.instance2 = instance2;
	}	
}

让我们看看上面的测试工具类。

package com.ddlab.rnd.patterns;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.InputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.OutputStream;

public class TestBreakSingleton 
{
	public static void main(String[] args) throws Exception
	{
		BreakSingleton bs = new BreakSingleton();

		OutputStream out = new FileOutputStream("data/a.ser");
		ObjectOutputStream oout = new ObjectOutputStream(out);
		oout.writeObject(bs);
		oout.flush();
		oout.close();
		out.flush();
		out.close();

		InputStream in = new FileInputStream("data/a.ser");
		ObjectInputStream oin = new ObjectInputStream(in);
		BreakSingleton bs1 = (BreakSingleton)oin.readObject();
		oin.close();
		in.close();

		System.out.println("Instance from Serialization :::"+bs1.getInstance2());
		System.out.println("Normal Instance :::"+SingletonType11.getInstance());

		InputStream in1 = new FileInputStream("data/a.ser");
		ObjectInputStream oin1 = new ObjectInputStream(in1);
		BreakSingleton bs2 = (BreakSingleton)oin1.readObject();
		oin1.close();
		in1.close();
		System.out.println("Another Instance from Serialization :::"+bs2.getInstance2());
	}

}

如果运行上述程序,则将获得以下类型的输出。

Instance from Serialization :::com.ddlab.rnd.patterns.SingletonType11@2586db54

Normal Instance :::com.ddlab.rnd.patterns.SingletonType11@12276af2

Another Instance from Serialization :::com.ddlab.rnd.patterns.SingletonType11@38a97b0b

因此,现在您获得了Singleton类的三个不同实例。 同样,我们遇到了多个实例的问题。 有什么办法可以使我们不会给黑客机会创建多个实例,而是可以序列化该对象? 哦,是的,有。 现在,让我们看一下修改后的单例Java类,以便我们能够序列化该对象,并且在任何时间点我们都将获得一致的单例类。

package com.ddlab.rnd.patterns;
import java.io.ObjectStreamException;
import java.io.Serializable;

public class SingletonType11 implements Serializable
{
	private static final long serialVersionUID = -4137189065490862968L;
	private static SingletonType11 instance = null;

	private SingletonType11()
	{
		super();
	}

	public static SingletonType11 getInstance()
	{
		if( instance == null )
			instance = new SingletonType11();
		return instance;
	}

	private Object readResolve() throws ObjectStreamException
	{
		return instance;
	}

	private Object writeReplace() throws ObjectStreamException
	{
		return instance;
	}
}

在上述方法中,我们将从序列化对象和“ getInstance()”方法的常规调用中获得一致的单例对象。 但是,我们仍然可以使用Reflection创建多个实例,并且由于要序列化对象而无法防止反射。 在这种情况下,我们可以向开发人员提出请求并达成协议,不要仅使用反射来避免反射策略。 开发人员达成了一项协议,不要使用反思来打破。

那么多线程或在多线程应用程序中使用单例呢? 让我们看看这里发生了什么。 让我们看看在Singleton类的情况下线程的使用。

让我们考虑一下我们前面讨论的第一个Singleton类。

package com.ddlab.rnd.patterns;
public class SingletonType1 
{
	private static SingletonType1 instance = null;

	private SingletonType1()
	{
		super();
	}

	public static SingletonType1 getInstance()
	{
		if( instance == null )
			instance = new SingletonType1();

		return instance;
	}
}

基本上,这种方法称为延迟初始化。 在多线程的情况下,如果处理不当,我们可以获得多个实例。 让我们看下面的代码。

package com.ddlab.rnd.patterns;
import java.util.Collections;
import java.util.HashSet;
import java.util.Set;

class Thread1 extends Thread
{
	@Override
	public void run() 
	{
		SingletonType1 instance = SingletonType1.getInstance();
		//		System.out.println("In Thread 1 - Singleton Instance ---->"+instance);
		TestSingletonType1_Thread.singletonSet.add(instance);
	}
}

class Thread2 extends Thread
{
	@Override
	public void run() 
	{
		SingletonType1 instance = SingletonType1.getInstance();
		//		System.out.println("In Thread 2 - Singleton Instance ---->"+instance);
		TestSingletonType1_Thread.singletonSet.add(instance);
	}
}

让我们看看测试类如何使用它。

public class TestSingletonType1_Thread 
{
	private static Set singletonSet1 = new HashSet();
	public static Set singletonSet = Collections.synchronizedSet(singletonSet1);

	public static void main(String[] args) 
	{
		//Singleton concept is broken here
		for( int i = 0 ; i < 100 ; i++ )
		{
			new Thread1().start();
			new Thread2().start();

			if( singletonSet.size() > 1 )
				break;
			else
				continue;
		}
		System.out.println(singletonSet);
	}
}

如果您多次运行上述程序,则将获得Singleton类的不同实例。

运行该程序后,您可能会得到类似的结果。 输出如下。

[com.ddlab.rnd.patterns.SingletonType1@60723d7c, com.ddlab.rnd.patterns.SingletonType1@6d9efb05, com.ddlab.rnd.patterns.SingletonType1@8dd20f6]

那么该怎么办 ? 我们可以声明volatile变量,现在让我们看看。 让我们拥有修改后的程序。

package com.ddlab.rnd.patterns;
public class SingletonType1 
{
	private static volatile SingletonType1 instance = null;

	private SingletonType1()
	{
		super();
	}

	public static SingletonType1 getInstance()
	{
		if( instance == null )
			instance = new SingletonType1();
		return instance;
	}
}

在多次运行该程序后,您可能会得到这样的信息。

[com.ddlab.rnd.patterns.SingletonType1@3f0ef90c, com.ddlab.rnd.patterns.SingletonType1@2e471e30]

但是,使用volatile不能满足我们的目的。 还是同样的问题,我们可以使用同步方法吗,是的,我们可以做到。 在许多情况下,大多数开发人员会提出与volatile关键字的用法及其在Singleton中的用法有关的问题。 如果经验不足的开发人员在其计算机上的第一次运行中获得上述类的单个实例,则可能会感到高兴。 我有很多开发人员在他们的机器上运行该程序是有道理的,他们也向我展示了。 这是正确的,因为他们很幸运。 但是我在他们的机器上多次运行了该程序,并告诉他们不要忘记事实。 现在,他们中的许多人开始使用java的great关键字和“同步的”生命保护程序来修改程序。 让我们看看这个关键字会发生什么。

让我们在下面看到。

package com.ddlab.rnd.patterns;
public class SingletonType1 
{
	private static volatile SingletonType1 instance = null;

	private SingletonType1()
	{
		super();
	}

	public static synchronized SingletonType1 getInstance()
	{
		if( instance == null )
			instance = new SingletonType1();

		return instance;
	}
}

但是会出现性能问题。 但是,当您对其进行分析时,您将意识到仅在第一次调用该方法时才需要同步。 后续调用不需要同步。 因此,不建议在每次调用时都使用关键字“ synchronized”。 从长远来看,它可能会对您的产品/项目开发产生不利影响。 为了提高上述程序的效率,让我们以不同的方式修改上述程序。 我们不会同步整个方法,而是会做敏感区域。

package com.ddlab.rnd.patterns;
public class SingletonType1 
{
	private static volatile SingletonType1 instance = null;

	private SingletonType1()
	{
		super();
	}

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

上面的程序看起来还不错,我们很高兴,现在让我们庆祝。 但是,由于存在一个非常大的问题,我们仍然远离艰苦的现实。 当instance为null时,两个线程可以同时进入if语句内部。 然后,一个线程进入同步块以初始化实例,而另一个则被阻塞。 当第一个线程退出同步块时,等待线程进入并创建另一个Singleton对象。 请注意,当第二个线程进入同步块时,它不会检查实例是否为非空。 让我们做一个小的临床测试来面对现实。

package com.ddlab.rnd.patterns;
import java.util.Collections;
import java.util.HashSet;
import java.util.Set;

class Thread11 extends Thread
{
	@Override
	public void run() 
	{
		SingletonType111 instance = SingletonType111.getInstance();
		//		System.out.println("In Thread 1 - Singleton Instance ---->"+instance);
		TestSingletonType111_Thread.singletonSet.add(instance);
	}
}

class Thread22 extends Thread
{
	@Override
	public void run() 
	{
		SingletonType111 instance = SingletonType111.getInstance();
		//		System.out.println("In Thread 2 - Singleton Instance ---->"+instance);
		TestSingletonType111_Thread.singletonSet.add(instance);
	}
}

public class TestSingletonType111_Thread 
{
	private static Set singletonSet1 = new HashSet();
	public static Set singletonSet = Collections.synchronizedSet(singletonSet1);

	public static void main(String[] args) 
	{
		//Singleton concept is broken here
		for( int i = 0 ; i < 100 ; i++ )
		{
			new Thread11().start();
			new Thread22().start();

			if( singletonSet.size() > 1 )
				break;
			else
				continue;
		}
		System.out.println(singletonSet);
	}

}

现在,您将多次了解上述程序。 接下来要做什么。

现在让我们考虑另一个被称为“双重检查锁定”的概念,该概念似乎对于一组开发人员来说是著名的。 许多开发人员在许多情况下都适用,并认为这是最强大的单例形式。

在软件工程中,双重检查锁定(也称为“双重检查锁定优化”)是一种软件设计模式,用于通过首先测试锁定标准(“锁定提示”)来减少获取锁定的开销,而无需实际获取锁。 只有在锁定

标准检查表明是否需要锁定,实际的锁定逻辑是否继续进行。 在大多数技术面试中,技术小组都希望候选人能给出这个答案。 如果候选人能够根据自己的喜好回答此问题,技术小组将很高兴并选择候选人。 如今,这个概念已变得非常重要,但是我要说的是技术小组对此概念没有足够的经验。 让我们对其进行深入分析。 “双重检查锁定”的基本结构如下。

public static SingletonType1 getInstance()
{
  if (instance == null)
  {
    synchronized(SingletonType1.class)  // Mark - 1
	{  
      if (instance == null)          // Mark - 2
        instance = new SingletonType1();  // Mark - 3
    }
  }
  return instance;
}

双重检查锁定背后的理论是// // Mark – 2处的第二次检查使得不可能创建两个不同的Singleton对象。 好的... 对于单线程应用程序可能是正确的。 细粒度的多线程应用程序呢? 让我们看下面的顺序。

线程1进入getInstance()方法。

线程1在// Mark – 1处进入同步块,因为实例为空。

线程1被线程2抢占。

线程2进入getInstance()方法。

线程2尝试获取// Mark – 1处的锁,因为实例仍然为null。 但是,由于线程1持有该锁,因此线程2在// Mark – 1处阻塞。

线程2被线程1抢占。

执行线程1,并且由于在// Mark – 2处instance仍然为空,因此创建了Singleton对象并将其引用分配给实例。

线程1退出同步块,并从getInstance()方法返回实例。

线程1被线程2抢占。

线程2获取// // Mark – 1处的锁,并检查instance是否为null。

由于instance非null,因此不会创建第二个Singleton对象,并且将返回线程1创建的对象。 双重检查锁定背后的理论是完美的。 不幸的是,现实是完全不同的。 双重检查锁定的问题在于不能保证它可以在单处理器或多处理器计算机上运行。 双重检查锁定失败的问题不是由于JVM中的实现错误,而是由于当前的Java平台内存模型。 内存模型允许所谓的“乱序写入”,这是该成语失败的主要原因。 但是,“乱序写入”的概念超出了我们的讨论范围。 最重要的是,不应使用任何形式的双重检查锁定,因为您不能保证它可以在任何JVM实现中使用。 如我们所见,虽然“双重检查锁定”可能有效,但可能会意外失败。 解决办法是什么 ?

Bill Pugh的解决方案

马里兰大学计算机科学研究员Bill Pugh(摘自Wikipedia)撰写了有关用Java实现Singleton模式的代码问题。 Pugh对“双重检查锁定”这一习惯用法的努力导致了Java 5中Java内存模型的变化,并导致了通常被视为在Java中实现Singletons的标准方法。 这种技术称为按需初始化持有人惯用语,它尽可能懒惰,并且可以在所有已知的Java版本中使用。 它利用了有关类初始化的语言保证,因此可以在所有Java兼容的编译器和虚拟机中正常工作。 嵌套类的引用不早于调用getInstance()的时间(因此,类加载器不会更早地对其进行加载)。 因此,该解决方案是线程安全的,不需要特殊的语言构造(即易失性或同步的)。

public class Singleton 
{
        // Private constructor prevents instantiation from other classes
        private Singleton() { }

        /**
        * SingletonHolder is loaded on the first execution of Singleton.getInstance() 
        * or the first access to SingletonHolder.INSTANCE, not before.
        */
        private static class SingletonHolder { 
                public static final Singleton INSTANCE = new Singleton();
        }

        public static Singleton getInstance() {
                return SingletonHolder.INSTANCE;
        }
}

上面称为“ 按需初始化持有人习惯用法 ”。 Singleton设计的上述结构在高度多线程的应用程序中非常强大。 让我们详细了解这个概念。 让我们考虑一个下面的小例子。

public class Something 
{
        private Something() 
       {
        }

        private static class LazyHolder
       {
                public static final Something INSTANCE = new Something();
        }

        public static Something getInstance() 
       {
                return LazyHolder.INSTANCE;
        }
}
怎么运行的

该实现依赖于Java虚拟机(JVM)内指定的执行初始化阶段。 有关详细信息,请参见Java语言规范(JLS)的12.4节。 当JVM加载Something类时,该类将进行初始化。 由于该类没有任何要初始化的静态变量,因此初始化很容易完成。 在JVM确定必须执行LazyHolder之前,不会初始化其中的静态类定义LazyHolder。 仅当在Something类上调用静态方法getInstance时,才执行静态类LazyHolder,并且第一次发生这种情况时,JVM将加载并初始化LazyHolder类。 LazyHolder类的初始化导致静态变量INSTANCE的执行是通过对外部类Something执行(私有)构造函数来进行的。 由于JLS保证类的初始化阶段是串行的,即非并发的,因此在加载和初始化期间,静态getInstance方法中不需要进一步的同步。 并且由于初始化阶段在串行操作中写入了静态变量INSTANCE,因此对getInstance的所有后续并发调用将返回相同的正确初始化的INSTANCE,而不会引起任何其他同步开销。

但是,使用“按需初始化持有人惯用语”模式的概念,我们可以实现线程安全的单例构造。 再次出现问题,我们可以反思地打破吗。 是的,我们可以使用我已经提到的java反射机制打破上述概念。 现在问题来了,是否还有其他方法可以构建适当的单例设计方法。 是的, Joshua Bloch(Google技术实验室首席技术架构师和著名的Book Effective Java的作者)建议使用另一种方法。

package com.ddlab.rnd.patterns;
public enum SingletonType3 
{
	INSTANCE;
	public void doSomething(String arg) 
	{
		//... perform operation here ...
	}
}

这是创建单例类的唯一可靠方法,该类是可序列化的,并且在默认情况下是完全线程安全的,而枚举是完全线程安全的。 关于反射,使用上面的反射方法,您不能破坏单例对象,因为它没有构造函数。 关于序列化,您将能够对其进行序列化,但是每次都会获得相同的实例。 因此,最后我们必须吸收这种创建Singleton设计类的现代方法。 但是,许多开发人员对此一无所知。

但是,大多数访调员不会接受以上两种方法,因为对他们而言,这可能是一个新概念。 您可以基于JLS和书籍参考进行辩论。 我的大三学生,同事和朋友每天都抱怨,在面试时这是他们通常在面试时面临的最困难的问题。 无论他们以何种方式回答问题,面试官都不会感到满意,这是因为在单例设计课程中,大多数人都不了解枚举的方法。 如果您也遇到相同的问题,请举约书亚·布洛赫(Joshua Bloch)为例。 您可能会遇到一些开发人员或访问员的问题,即“ Singleton类必须具有私有构造函数,并且应该有一个名为getInstance()的方法”。 您必须论证说下划线语句是错误的,并且它不是协议或任何经验法则。 这只是我们一段时间以来采用的一种方法。 单例背后的主要概念是在任何时间点,都应该只有一个实例,与如何编写代码无关。 如果面试不断与您争论,您会问他将枚举定义为单例方法的问题所在。 胆小的面试官可能会提出一些无稽之谈。 最后,您告诉他,在JDK 5中,枚举由Josh Bloch和Neal Gafter编写。 如果开发人员或面试官有胆量,他可以将邮件发送给这些优秀的建筑师。 如果傲慢的面试官仍在作出错误的论点,请教给他一堂课,“先生,您告诉我单身人士的做法,这是无法打破的。 至少我会以各种方式破坏您的Singleton设计。”

仍然不能使用枚举破坏上述单例方法,但是我们可以通过编写代码来创建多个实例来破解上述方法。 下面给出的代码请勿将以下代码用于您的商业产品。 这是打破单例的讨厌方法。 让我们看下面的代码。

package com.ddlab.rnd.patterns;
import java.lang.reflect.Constructor;
import java.lang.reflect.Method;
import java.util.HashSet;
import java.util.Set;
import sun.reflect.ConstructorAccessor;

public class CrackEnumSingleton 
{
	public static void main(String[] args)
	{
		Set set = new HashSet();
		try 
		{
			SingletonType3 firstInstance = SingletonType3.INSTANCE;
			System.out.println(firstInstance.getClass() + " " + firstInstance + " = " + System.identityHashCode(firstInstance));
			set.add(firstInstance);

			Constructor constructor = SingletonType3.class.getDeclaredConstructors()[0];
			Method acquire = constructor.getClass().getDeclaredMethod("acquireConstructorAccessor");//"acquireConstructorAccessor" fields for cracking
			acquire.setAccessible(true);
			acquire.invoke(constructor);

			Method get = constructor.getClass().getDeclaredMethod("getConstructorAccessor");//"getConstructorAccessor" fields for cracking
			get.setAccessible(true);
			ConstructorAccessor invoke = (ConstructorAccessor) get.invoke(constructor);
			Object secondInstance = invoke.newInstance(new Object[] {null,1});
			System.out.println(secondInstance.getClass() + " " + secondInstance + " = " + System.identityHashCode(secondInstance));
			set.add(secondInstance);

			System.out.println("Total No of Singletons :::"+set.size());
		}
		catch (Exception e) 
		{
			e.printStackTrace();
		}
	}

}

但是,上述方法只是一种学习技术,而并非在任何地方实现。 在这个世界上,每个对象都具有积极和消极的态度,但是我们必须遵循积极的态度才能顺利开发我们的产品或项目。

结论

本文没有任何商业意义。 在本文中,我提供了编写更好的Singleton设计类的更好方法。 可能有最佳方法,如果您知道其他最佳方法或最佳做法,请与我分享。 还提供一些注释,以便我们可以为更好的编码标准做出更好的贡献。 希望您喜欢我的文章。 如有任何错误,请通过debadatta.mishra@gmail.com向我报告。 谢谢。

参考: Singleton设计模式– Debadatta Mishra博客上来自我们JCG合作伙伴 Debadatta Mishra的鸟瞰图

翻译自: https://www.javacodegeeks.com/2013/06/singleton-design-pattern-a-lions-eye-view.html

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值