单例设计模式示例

本文是我们名为“ Java设计模式 ”的学院课程的一部分。

在本课程中,您将深入研究大量的设计模式,并了解如何在Java中实现和利用它们。 您将了解模式如此重要的原因,并了解何时以及如何应用模式中的每一个。 在这里查看

1.单例模式

有时对于某些类来说,只有一个实例很重要。 有许多对象,我们只需要它们的一个实例,如果实例化多个对象,我们将遇到各种问题,例如程序行为不正确,资源过度使用或结果不一致。

您可能只需要一个类的对象,例如,当您创建应用程序的上下文时,或者线程可管理的池,注册表设置,连接到输入或输出控制台的驱动程序等。该类型显然会导致程序不一致。

Singleton模式可确保一个类只有一个实例,并提供对其的全局访问点。 但是,尽管就类图而言,单例是最简单的,因为只有一个类,但其实现却有些棘手。

图1

图1

在本课程中,我们将尝试不同的方法来仅创建类的单个对象,还将了解一种方法比另一种方法更好。

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;
	}
}

除此之外,还有其他打破单例模式的方法。

  1. 如果该类是Serializable。
  2. 如果是可克隆的。
  3. 可以通过反思来打破。
  4. 同样,如果该类由多个类加载器加载。

下面的示例说明如何保护您的类免于被多次实例化。

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));
	}
	
}
  1. 在您的单例类中实现readResolve()writeReplace()方法,并通过它们返回相同的对象。
  2. 您还应该实现clone()方法并引发异常,以使单例无法被克隆。
  3. 构造函数中的“ if条件”可以防止使用反射多次实例化单例。
  4. 为了防止从不同的类加载器实例化单例,可以实现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

  1. 一个类必须完全有一个实例,并且必须可以从众所周知的访问点访问它。
  2. 当唯一的实例可以通过子类扩展,并且客户端应该能够使用扩展的实例而无需修改其代码。

4.下载源代码

这是有关Singleton Pattern的课程。 您可以在此处下载源代码: SingletonPattern-Project

翻译自: https://www.javacodegeeks.com/2015/09/singleton-design-pattern.html

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值