设计模式之--单例模式【小白日记】

概念

一个类有且仅有一个实例,并自行实例化向整个系统提供这个实例。

使用场景

确保某个类有且仅有一个类的实例(对象),避免产生过多的实例消耗资源,或者某些需求中,只需一个对象的情况。譬如,
IO操作或者数据库连接类的相关对象,对性能消耗过大地操作,或者频繁地创建和销毁的对象,就需要考虑是否使用该模式。

特点

  1. 构造函数不对外开放,修饰符为private;
  2. 单例类的实例(对象)有且仅有一个,尤其是多线程中;
  3. 确保单例类对象在反序列化时不会重新构建对象;
  4. 单例类必须自己创建自己的唯一实例(通过静态方法或者枚举返回单例类实例)。

注意:getInstance()方法中需要使用同步锁 synchronized (Singleton.class) 防止多线程同时进入造成instance被多次实例化。

优点

  1. 系统内存里只有一个对象实例,降低内存消耗;
  2. 避免对资源的多重占用,比如对文件的读写。

缺点

  1. 没有接口,也无法继承;
  2. 不符合单一职责原则,一个类应该只关心内部实现逻辑,而不应该关心外部如何去实例化它;

简单示例

public class SingletonClass {
	// 创建SingletonClass实例
	private static SingletonClass singleInstance = new SingletonClass();

	// 私有的构造函数,防止通过构造函数实例化对象
	private SingletonClass(){}

	// 获取实例
	public static SingletonClass getInstance() {
		return singleInstance;
	}  
} 

调用方式

public class SingletonPatternTest {
   public static void main(String[] args) {
      //获取唯一可用的对象
      SingletonClass singletonObj = SingletonClass.getInstance();
   }
}

其他实现方式

  • 懒汉模式一

    懒汉模式是声明一个静态对象,并且在用户第一次调用getInstance时进行初始化。这种方式是最基本的实现方式,由于没有加同步锁 synchronized,所以它不支持多线程。严格来讲,算不上单例模式。

// 懒汉模式一,不要求线程安全,不适用于多线程中
public class SingletonClass {
	private static SingletonClass singleInstance;
	
	private SingletonClass(){}

	public static SingletonClass getInstance() {
		if (null == singleInstance) {
			singleInstance = new SingletonClass();
		}
		return singleInstance;
	}  
} 
  • 懒汉模式二

    此方式添加了同步锁 synchronized,所以它能够支持多线程。但是,效率很低,99% 情况下不需要同步。

// 懒汉模式二,要求线程安全,适用于多线程中
public class SingletonClass {
	private static SingletonClass singleInstance;
	
	private SingletonClass(){}

	public static synchronized SingletonClass getInstance() {
		if (null == singleInstance) {
			singleInstance = new SingletonClass();
		}
		return singleInstance;
	}  
} 
总结:懒汉模式的优点是单例只有在使用时才被实例化,在一定程度上节约了资源开销;缺点是第一次加载时需要即时进行实例化,反应稍慢,
而且每次调用getInstance都进行同步,造成不必要的同步开销,这种模式不推荐使用。
  • 饿汉模式

    此模式基于classloader机制避免了多线程的同步问题,但是,instance在加载的时候就已经实例化了,导致类装载的方式有很多种,在单例模式中,大多数都是调用getInstance方法导致的,当然也不排除其他方式(或者其他静态方法)导致类装载。这时候初始化显然是没有达到懒加载的效果。

public class SingletonClass {
	private static SingletonClass singleInstance = new SingletonClass();
	
	private SingletonClass(){}

	public static SingletonClass getInstance() {		
		return singleInstance;
	}  
} 
总结:
	优点:没有同步锁,执行效率高;
	缺点:类加载时就初始化,浪费内存资源;
  • Double Checke Lock(DCL)双重校验锁/双检锁模式

    此模式采用双锁机制,安全且在多线程情况下能保持高性能。既能在需要的时才初始化单例,又能保证线程安全,且对象实例化后调用getInstance不进行同步锁。getInstance()的性能对应用程序很关键。

public class SingletonClass {

	private static volatile SingletonClass singleInstance = null;
	
	private SingletonClass(){}

	public static SingletonClass getInstance() {
		if (null == singleInstance) {// 避免不必要的同步
			synchronized (SingletonClass.class) {
				if (null == singleInstance) {// 在未实例化的情况下才创建实例
					singleInstance = new SingletonClass();
				}
			}
		}
		
		return singleInstance;
	}  
} 
总结:
	DCL优点:资源利用率高,第一次执行getInstance时单例对象才会被初始化,效率高。
	
	DCL缺点:第一次加载反应稍慢,也由于Java内存模型的原因偶尔会失败(Java编译器允许处理器乱序执行程序指令,JDK1.6后修复),
	在高并发情况下也有一定的缺陷,但是发生频率很小。
	
	此模式是使用最多的单例实现方式,它能够在需要时才实例化单例对象,并且能够在绝大多数场景中保证单例对象的唯一性,
	除非你的代码在并发场景比较复杂或低于JDK6版本下使用,否则,此方式一般能够满足需求。
  • 静态内部类模式

    静态内部类模式主要用于弥补DCL模式的缺陷(即DCL失效)。

public class SingletonClass {
	
	private SingletonClass(){}

	public static SingletonClass getInstance() {
		return SingletonHolder.singleInstance;
	}

	/** 静态内部类 */
	private static class SingletonHolder {
		private static final SingletonClass singleInstance = new SingletonClass();
	} 
} 
总结:
	静态内部类模式,首次加载SingletonClass类时并不会初始化singleInstance,
	只有在第一次的调用getInstance()方法时才会加载SingletonHolder类,这种方式不仅能够确保线程安全,
	也能够保证单例对象的唯一性,同时也延迟了单例的实例化。推荐使用的单例模式。
  • 枚举模式

    枚举模式是最简单的单例实现方式,也是最佳的单例实现方式。简洁,自动支持序列化机制,绝对防止多次实例化。由于 JDK1.5 之后才加入 enum 特性,用这种方式写不免让人感觉生疏,在实际工作中,也很少用。

public enum SingletonClass {
	INSTANCE;
	public void doSth() {
		// TO DO
	}
} 
总结:枚举单例模式的最大优点就是写法非常简单。枚举在Java中与普通的类一样,不仅能够有字段,还能够有自己的方法,
最重要的是默认枚举实例的创建是线程安全的,并且在任何情况下都是一个单例。
  • 容器模式

    容器模式,是将不同的单例对象放在Map中,通过key获取对象对应的类型的对象。代码如下:

public class SingletonClass {

	private static Map map = new HashMap();

	private SingletonClass() {}
	
	public static void registerService(String key, Object instance) {
		if (!map.containsKey(key)) {
			map.put(key, instance);
		}
	}

	public static Object getService(String key) {
		return map.get(key);
	}
} 
总结:容器模式可以管理多种类型的单例,并且在使用时可以通过统一的接口进行获取操作,降低使用成本,隐藏了具体实现,
降低了耦合度。

正常情况下,建议使用【饿汉模式】,如果要明确实现懒加载效果,可使用【静态内部类模式】,如果涉及反序列化创建对象时,建议采用【枚举模式】,如果想管理多种类型的单例,可以考虑【容器模式】,特殊需求,可以试试【双检锁模式】。


注:本帖子相关内容参考于网络,侵删。

你可能感兴趣的:(程序设计)