java设计模式-单例模式

Source

保证一个类只有一个实例,并提供一个唯一的全局访问点。

1、介绍

单例模式的功能

       保证这个类在运行期间只会被创建一个类实例,并提供一个全局唯一访问这个类实例的访问点。

单例模式的范围

       是一个ClassLoader及其子ClassLoader的范围。

单例模式的命名

       一般建议单例模式的方法命名为:getInstance()。

单例模式的本质

       控制实例数目

何时选用单例模式

       当需要控制一个类的实例只能有一个,而且客户只能从一个全局访问点访问它时,可以选择单例模式。

单例模式使用场景

  1. 配置文件的读取,由于配置文件是共享资源。
  2. 应用程序的日志记录,日志属于共享文件,只能有一个实例去操作。
  3. 数据库连接池,多线程线程池。
  4. 网站计数器,否则难以同步。

2、结构说明

Singleton:

       负责创建Singleton类自己的唯一实例,并提供一个getInstance的方法,让外部来访问这个类的唯一实例。

3、单例模式实现方式

懒汉式、饿汉式、双重检查加锁、枚举类实现

单例模式调用顺序示意图

一种更好的单例实现方式

Lazy initialization holder class模式,这个模式综合使用了Java的类级内部

类和多线程缺省同步锁的知识,很巧妙的同时实现了延迟加载和线程安全。

单例和枚举

Singleton的最佳方法。

(1)Java的枚举类型实质上是功能齐全的类,因此可以有自己的属性和方法

(2)Java枚举类型的基本思想:通过公有的静态final域为每个枚举常量导出实

例的类

(3)从某个角度讲,枚举是单例的泛型化,本质上是单元素的枚举

用枚举来实现单例非常简单,只需要编写一个包含单个元素的枚举类型即可

4、单例模式中的编程思想

延迟加载思想

       一开始不加载资源或数据,一直等到马上要使用这个资源或数据了才去加载,也称Lazy Load,尽可能节约资源。

缓存思想

       某些资源或数据会被频繁使用,可以将这些数据缓存到内存中,每次操作时,先到内存里面找,如果有就直接使用,如果没有就获取它,并设置到缓存中,下一次访问的时候就可以直接从内存中获取了。从而节省大量的时间,缓存是一种典型的空间换时间的方案。

5、单例模式优缺点

  1. 时间和空间:懒汉式典型的时间换空间,饿汉式空间换时间。
  2. 线程安全:

不加同步的懒汉式是线程不安全的

       饿汉式是线程安全的,因为虚拟机保证了只会装载一次。

       如何实现懒汉式线程安全,加上synchronized即可。

       双重检查锁

       所谓双重检查加锁机制,指的是:并不是每次进入getInstance方法都需要同步,而是先不同步,进入方法过后,先检查实例是否存在,如果不存在才进入下面的同步块,这是第一重检查。进入同步块过后,再次检查实例是否存在,如果不存在,就在同步的情况下创建一个实例,这是第二重检查。这样一来,就只需要同步一次了,从而减少了多次在同步情况下进行判断所浪费的时间。

       双重检查加锁机制的实现会使用一个关键字volatile,它的意思是:被volatile修饰的变量的值,将不会被本地线程缓存,所有对该变量的读写都是直接操作共享内存,从而确保多个线程能正确的处理该变量。

注意:在Java1.4及以前版本中,很多JVM对于volatile关键字的实现有问题,会导致双重检查加锁的失败,因此本机制只能用在Java5及以上的版本。

6、代码示例

/**
 * 使用枚举来实现单例模式的示例
 */
public enum Singleton {	
	/**
	 * 定义一个枚举的元素,它就代表了Singleton的一个实例
	 */
	uniqueInstance;
	
	/**
	 * 示意方法,单例可以有自己的操作
	 */
	public void singletonOperation(){
		//功能处理
		System.out.println("aa=="+Singleton.uniqueInstance.hashCode());
	}
}

/**
 * 单例示例   懒汉式
 */
public class Singleton {
	/**
	 * 定义一个变量来存储创建好的类实例
	 */
	private static Singleton uniqueInstance = null;
	/**
	 * 私有化构造方法,好在内部控制创建实例的数目
	 */
	private Singleton(){
		
	}
	/**
	 * 定义一个方法来为客户端提供类实例
	 * @return 一个Singleton的实例
	 */
	public static synchronized Singleton getInstance(){
		//判断存储实例的变量是否有值
		if(uniqueInstance == null){
			//如果没有,就创建一个类实例,并把值赋值给存储类实例的变量
			uniqueInstance = new Singleton();
		}
		//如果有值,那就直接使用
		return uniqueInstance;
	}
	/**
	 * 示意方法,单例可以有自己的操作
	 */
	public void singletonOperation(){
		//功能处理
	}
	/**
	 * 示意属性,单例可以有自己的属性
	 */
	private String singletonData;
	/**
	 * 示意方法,让外部通过这些方法来访问属性的值
	 * @return 属性的值
	 */
	public String getSingletonData(){
		return singletonData;
	}
}

/**
 * 饿汉式单例实现的示例
 */
public class Singleton {
	/**
	 * 定义一个变量来存储创建好的类实例,直接在这里创建类实例,只会创建一次
	 */
	private static Singleton uniqueInstance = new Singleton();
	/**
	 * 私有化构造方法,好在内部控制创建实例的数目
	 */
	private Singleton(){
		
	}
	/**
	 * 定义一个方法来为客户端提供类实例
	 * @return 一个Singleton的实例
	 */
	public static Singleton getInstance(){
		//直接使用已经创建好的实例
		return uniqueInstance;
	}
	
	/**
	 * 示意方法,单例可以有自己的操作
	 */
	public void singletonOperation(){
		//功能处理
	}
	/**
	 * 示意属性,单例可以有自己的属性
	 */
	private String singletonData;
	/**
	 * 示意方法,让外部通过这些方法来访问属性的值
	 * @return 属性的值
	 */
	public String getSingletonData(){
		return singletonData;
	}
}

/**
 * 使用缓存来模拟实现单例
 */
public class Singleton {
	/**
	 * 定义一个缺省的key值,用来标识在缓存中的存放
	 */
	private final static String DEFAULT_KEY = "One";
	/**
	 * 缓存实例的容器
	 */
	private static Map<String,Singleton> map = new HashMap<String,Singleton>();
	/**
	 * 私有化构造方法
	 */
	private Singleton(){
		//
	}
	public static Singleton getInstance(){
		//先从缓存中获取
		Singleton instance = (Singleton)map.get(DEFAULT_KEY);
		//如果没有,就新建一个,然后设置回缓存中
		if(instance==null){
			instance = new Singleton();
			map.put(DEFAULT_KEY, instance);
		}
		//如果有就直接使用
		return instance;
	}
	
	public static void main(String[] args) {
		
		for(int i=0;i<3;i++){
			System.out.println(Singleton.getInstance());
		}
		
	}
}