本文是我们名为“ Java设计模式 ”的学院课程的一部分。
在本课程中,您将深入研究大量的设计模式,并了解如何在Java中实现和利用它们。 您将了解模式如此重要的原因,并了解何时以及如何应用模式中的每一个。 在这里查看 !
1.单例模式
有时对于某些类来说,只有一个实例很重要。 有许多对象,我们只需要它们的一个实例,如果实例化多个对象,我们将遇到各种问题,例如程序行为不正确,资源过度使用或结果不一致。
您可能只需要一个类的对象,例如,当您创建应用程序的上下文时,或者线程可管理的池,注册表设置,连接到输入或输出控制台的驱动程序等。该类型显然会导致程序不一致。
Singleton模式可确保一个类只有一个实例,并提供对其的全局访问点。 但是,尽管就类图而言,单例是最简单的,因为只有一个类,但其实现却有些棘手。
在本课程中,我们将尝试不同的方法来仅创建类的单个对象,还将了解一种方法比另一种方法更好。
2.如何使用单例模式创建类
创建这种类型的类的方法有很多种,但仍然可以看到一种方法比另一种方法更好。
让我们从一个简单的方法开始。
如果提供一个使对象可访问的全局变量该怎么办? 例如:
package com.javacodegeeks.patterns.singletonpattern;
public class SingletonEager {
public static SingletonEager sc = new SingletonEager();
}
众所周知,一个类的静态变量只有一个副本,我们可以应用它。 就目前而言,客户端代码使用此sc
静态变量就可以了。 但是,如果客户端使用新的运算符,则将有此类的新实例。
要停止在类之外实例化该类,让我们将该类的构造函数设为私有。
package com.javacodegeeks.patterns.singletonpattern;
public class SingletonEager {
public static SingletonEager sc = new SingletonEager();
private SingletonEager(){}
}
这行得通吗? 我想是的。 通过使构造函数保持私有状态,其他任何类都不能实例化该类。 获取此类的唯一方法是使用sc
静态变量,该变量确保只有一个对象存在。
但是,众所周知,直接访问类成员不是一个好主意。 我们将提供一种方法,通过该方法,sc变量将获得访问权,而不是直接访问。
package com.javacodegeeks.patterns.singletonpattern;
public class SingletonEager {
private static SingletonEager sc = new SingletonEager();
private SingletonEager(){}
public static SingletonEager getInstance(){
return sc;
}
}
因此,这是我们的单例类,可确保仅创建该类的一个对象,即使存在多个请求,也将仅返回相同的实例化对象。
这种方法的一个问题是,一旦将类加载到JVM中,就会立即创建对象。 如果从未请求过该对象,则内存中将有一个无用的对象。
在需要时创建对象始终是一种好方法。 因此,我们将在第一个调用上创建一个对象,然后在其他后续调用上返回相同的对象。
package com.javacodegeeks.patterns.singletonpattern;
public class SingletonLazy {
private static SingletonLazy sc = null;
private SingletonLazy(){}
public static SingletonLazy getInstance(){
if(sc==null){
sc = new SingletonLazy();
}
return sc;
}
}
在getInstance()
方法中,我们检查静态变量sc
是否为null,然后实例化该对象并将其返回。 因此,在第一次调用时,如果sc为null,则将创建对象,而在接下来的后续调用中,它将返回相同的对象。
这看起来确实不错,不是吗? 但是,此代码将在多线程环境中失败。 想象两个线程同时访问该类,线程t1首次调用getInstance()
方法,它检查静态变量sc是否为null,然后由于某种原因而被中断。 另一个线程t2调用getInstance()
方法成功通过了if检查并实例化了该对象。 然后,线程t1醒来,它还创建了对象。 此时,将有两个此类。
为了避免这种情况,我们将使用synchronized
关键字到getInstance()
方法。 通过这种方式,我们强制每个线程等待其轮流才能进入该方法。 因此,没有两个线程会同时进入该方法。 同步带有价格,它将降低性能,但是如果对getInstance()
方法的调用不会给您的应用程序造成实质性开销,则请忽略它。 另一个解决方法是转向渴望的实例化方法,如前面的示例所示。
package com.javacodegeeks.patterns.singletonpattern;
public class SingletonLazyMultithreaded {
private static SingletonLazyMultithreaded sc = null;
private SingletonLazyMultithreaded(){}
public static synchronized SingletonLazyMultithreaded getInstance(){
if(sc==null){
sc = new SingletonLazyMultithreaded();
}
return sc;
}
}
但是,如果要使用同步,则有另一种称为“双重检查锁定”的技术可以减少同步的使用。 使用双重检查锁定,我们首先检查是否创建了实例,如果没有创建,则进行同步。 这样,我们只同步第一次。
package com.javacodegeeks.patterns.singletonpattern;
public class SingletonLazyDoubleCheck {
private volatile static SingletonLazyDoubleCheck sc = null;
private SingletonLazyDoubleCheck(){}
public static SingletonLazyDoubleCheck getInstance(){
if(sc==null){
synchronized(SingletonLazyDoubleCheck.class){
if(sc==null){
sc = new SingletonLazyDoubleCheck();
}
}
}
return sc;
}
}
除此之外,还有其他打破单例模式的方法。
- 如果该类是Serializable。
- 如果是可克隆的。
- 可以通过反思来打破。
- 同样,如果该类由多个类加载器加载。
下面的示例说明如何保护您的类免于被多次实例化。
package com.javacodegeeks.patterns.singletonpattern;
import java.io.ObjectStreamException;
import java.io.Serializable;
public class Singleton implements Serializable{
private static final long serialVersionUID = -1093810940935189395L;
private static Singleton sc = new Singleton();
private Singleton(){
if(sc!=null){
throw new IllegalStateException("Already created.");
}
}
public static Singleton getInstance(){
return sc;
}
private Object readResolve() throws ObjectStreamException{
return sc;
}
private Object writeReplace() throws ObjectStreamException{
return sc;
}
public Object clone() throws CloneNotSupportedException{
throw new CloneNotSupportedException("Singleton, cannot be clonned");
}
private static Class getClass(String classname) throws ClassNotFoundException {
ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
if(classLoader == null)
classLoader = Singleton.class.getClassLoader();
return (classLoader.loadClass(classname));
}
}
- 在您的单例类中实现
readResolve()
和writeReplace()
方法,并通过它们返回相同的对象。 - 您还应该实现
clone()
方法并引发异常,以使单例无法被克隆。 - 构造函数中的“ if条件”可以防止使用反射多次实例化单例。
- 为了防止从不同的类加载器实例化单例,可以实现
getClass()
方法。 上面的getClass()
方法将类加载器与当前线程关联; 如果该类加载器为null,则该方法使用与加载单例类相同的类加载器。
尽管我们可以使用所有这些技术,但是有一种简单的方法可以创建单例类。 从JDK 1.5开始,您可以使用枚举创建单例类。 枚举常量是隐式静态的和最终的,创建后就无法更改其值。
package com.javacodegeeks.patterns.singletonpattern;
public class SingletoneEnum {
public enum SingleEnum{
SINGLETON_ENUM;
}
}
当您尝试显式实例化Enum对象时,将出现编译时错误。 当Enum静态加载时,它是线程安全的。 Enum中的clone方法是最终的,可确保枚举常量永远不会被克隆。 枚举本质上是可序列化的,序列化机制可确保不会因反序列化而创建重复的实例。 也禁止使用反射实例化。 这些确保了枚举实例不存在超出枚举常量定义的实例。
3.何时使用Singleton
- 一个类必须完全有一个实例,并且必须可以从众所周知的访问点访问它。
- 当唯一的实例可以通过子类扩展,并且客户端应该能够使用扩展的实例而无需修改其代码。
4.下载源代码
这是有关Singleton Pattern的课程。 您可以在此处下载源代码: SingletonPattern-Project
翻译自: https://www.javacodegeeks.com/2015/09/singleton-design-pattern.html