Java设计模式——单例模式


单例模式是我们在开发中最常用的设计模式之一,也是较为简单的一种设计模式,虽然简单但是里面也有不少道理可以探寻。

定义:

单例,顾名思义,就是类的对象实例只有一个,所以,单例模式必须确保一个类只有一个实例,然后一个类可以自己去实例化自己并且向全局提供这个唯一的实例。



使用场景:

当产生的对象需要消耗太多的资源,或者你这个对象全局只能有一个的时候可以考虑使用单例模式。




类型:

单例模式又可以大致分为:

饿汉模式,一般懒汉模式,加强懒汉([Double CheckLock] DCL)模式,静态内部类模式,枚举模式,容器模式等




具体实现方式【代码里有详细的注释,直接看代码就OK啦】:

1. 先来看最简单的饿汉模式:

public class Singleton{
	
	//饿汉模式之所以叫饿汉,顾名思义,它比较饿,它就比较急,所以,在类一开始就直接new对象
	//静态final对象也很关键,提供全局不可变的对象,是单例实现成败的关键
	private static final Singleton instance = new Singleton();
	
	//私有化构造方法,这个不用说,是单例的前提,防止别人直接new
	private Singleton(){}
	
	//提供一个public的方法供别人获取这个单例对象
	public static Singleton getInstance(){
		return instance;
	}	
}




2. 一般懒汉模式:

public class Singleton{
	
	private static Singleton instance = null;
	
	private Singleton(){}
	
	//看到synchronized了吗? 这里说明getInstance是一个同步方法,保证在多线程的情况下单例依然有效
	//但是,这种模式有很大的问题,就是每次get都要synchronized一次,这样会额外增加开销,效率并不高!
	public static synchronized Singleton getInstance(){
		//这里的判空防止多次get的时候多次new对象
		if(instance = null){
			//懒汉,顾名思义,它比较懒,所以,你什么时候需要我再new对象
			instance = new Singleton();
		}
		return instance;
	}
	
}



3.加强懒汉(DCL)模式:

public class Singleton{
	
	private static Singleton instance = null;
	
	private Singleton(){}
	
	//加强懒汉模式的优点是在需要的时候才new对象,而且能够保证线程安全,同时在get的时候不会每次都加同步锁
	public static  Singleton getInstance(){
		//第一次判空,避免不必要的同步
		if(instance = null){
			synchronized(Singleton.class){
				//第二次判空,看到这里,是不是有些迷糊了?
				//我们知道,new操作是一个不可中断的操作,除非构造方法有问题,里面涉及到3个步骤:
				//1.给实例分配内存
				//2.调用构造方法,初始化对象
				//3.将实例化后的对象指向刚分配的内存空间
				//这里,我们知道由于Java编译器允许乱序执行以及JVM中缓存到内存写回顺序的原因导致上面123的顺序
				//并不能保证每次都执行,所以在某些特定的时刻会导致单例失效!!!
				if(instance = null){
					instance = new Singleton();
				}
			}
		}
		
	}
     return instance;	
}




4. 静态内部类模式:

public class Singleton{
	
	private Singleton(){}
	
	//这是一个静态的内部类,由于静态内部类的特性导致在getInstance的时候JVM才加载SingletonHolder类,而且只加载一次
	//又由于静态内部类天然的线程安全,所以可以保证单例的成功,同时也实现了延迟加载
	private static class SingletonHolder{
		private static final Singleton instance = new Singleton();
	}
	
	//加强版懒汉模式虽然解决了多余同步以及线程安全的问题,但是在某些特定的情况下依然会出现单例失败的情况,也就是所谓的的双重检查锁定(DCL)失效。
	public static Singleton getInstance(){
		return SingletonHolder.instance;
	}
	
}





5. 枚举模式:

//你看到了什么?没错,就是枚举!!
//枚举模式简单无比,枚举实例在Java中默认线程安全,而且,之前说的那些模式无不在反序列化的时候会破坏单例(反序列化的时候可以通过特殊的手段创建新实例)
//但是,枚举就不会存在这个问题,反序列化并不会对枚举对象产生任何影响。
public enum SingletonEnum{
	INSTANCE;
}





6. 容器模式:

//事实上,通过SingletonManager来管理所有的单例,之后你想获取单例对象可以直接通过SingletonManager来获取
//省去了在到每个类中去get的麻烦,也避免了重复创建对象,通过一个统一的Manager,可以降低难度,同时也降低耦合!
public class SingletonManager{
	
	//先来个HashMap,用来保存单例对象
	private static Map<String,Object> map = new HashMap<String,Object>();
	
	private Singleton(){}
	
	//通过add方法将单例对象添加到HashMap中
	public static void addInstance(String key,Object instance){
		if(!map.containsKey(key)){
			map.put(key,instance);
		}
	}
	
	//通过get方法获取你想要的单例对象
	public static getInstance(String key){
		return map.get(key);
	}
	
}



通过以上6种方式,我们可以看到,实现单例主要有几个关键点:

 (1)构造方法私有化

 (2)通过一个静态方法或枚举返回单例对象

 (3)确保单例类对象有且只有一个,尤其还要保证线程安全

 (4)确保单例类对象在反序列化时不会重新生成对象

在实际应用中,具体选择哪种方式要取决于项目本身,比如说,项目是不是存在复杂的高并发环境,JDK的版本是不是太低,单例对象的创建是否太消耗资源等等。


 


单例模式的缺点:

(1)单例模式一般没有接口导致拓展很难

(2)在Android中,单例对象如果使用Context不当,很容易出现内存泄漏的问题