设计模式学习 - 单例模式

定义:保证一个类仅有一个实例,并提供一个访问它的全局访问点。


Singleton类,定义了一个getInstance方法,允许客户访问该方法获取唯一实例,getInstance是一个静态方法,主要负责创建自己及的唯一实例。

单例模式的实现写法

饿汉模式

public class Singleton {
	
	//类加载时就会初始化 ,使用了static final进行修饰实例常量,也就是该常量不可改变了而是唯一的
	private static final Singleton instance=new Singleton();

	//私有构造函数,防止外部调用,确保实例的唯一性
	private Singleton() {

	}
	
	//静态方法获取实例
	public static Singleton getInstance(){
		return instance;
	}
	
}

用“饿”来形容该模式,感觉很贴切,既然是很饿,肯定很急,需要马上创建实例来缓解饿的程度,不然呵呵。饿汉模式的特点:

主要是通过static final修饰静态实例常量来实现的,所以类加载时就会被初始化实例,基于类加载机制,该实现是线程安全的。同时也是由于类加载的时候已经初始化了实例,在没有调用getInstance方法也会加载到内存中,没有达到懒加载的效果,如果自始至终都没有使用该实例,这样就造成了内存的浪费。

懒汉模式(线程不安全)

public class Singleton {
	
	//声明了一个静态对象,初始状态是空对象
	private static  Singleton instance;

	//私有构造函数,防止外部调用,确保实例的唯一性
	private Singleton() {

	}
	
	//每次调用会先判断对象是否空,若为空则创建(一般是第一次调用被创建),这个方式一定程度节约了资源
	//但是也存在缺陷,就是在多线程并行访问时,会创建多个实例,存在线程安全问题
	public static Singleton getInstance(){
		if (instance==null) {
			instance=new Singleton2();
		}
		return instance;
	}
	
}

用“懒”字来形容该模式,是十分贴切的,在需要的时候才会去创建实例,平时不用的情况是不会急着创建实例的,觉得懒人都这样,等需要的才去做,匆匆忙忙的做,结果问题就出来了。懒汉模式的特点:

首先声明了一个空实例的静态对象,在初次调用的时候才会被实例化。达到了懒加载的效果,节约了资源;但缺陷也是存在的,如果多线程并行访问,存在线程安全问题。

我们可以通过例子来验证是非线程安全:

public class Test2 {
	public static void main(String[] args) {
			Test2 test2 = new Test2();
			new Thread(test2.new Test1Runable()).start();
			new Thread(test2.new Test2Runable()).start();
	}
	class Test1Runable implements Runnable{
		@Override
		public void run() {
			Singleton2 instance = Singleton2.getInstance();
			System.out.println("Test1对应的hashCode值:"+instance.hashCode());
		}
		
	}
	
	class Test2Runable implements Runnable{
		@Override
		public void run() {
			Singleton2 instance = Singleton2.getInstance();
			System.out.println("Test2对应的hashCode值:"+instance.hashCode());
		}
		
	}

}

运行结果:

Test1对应的hashCode值:950839416
Test2对应的hashCode值:856722497
从结果可以看出,两次的hashCode值是不一样的,很明显是创建了两个实例。

懒汉模式(线程安全)

public class Singleton {
	
	//声明了一个静态对象,初始状态是空对象
	private static  Singleton instance;

	//私有构造函数,防止外部调用,确保实例的唯一性
	private Singleton() {

	}
	
	//基于懒汉模式线程不安全问题进行改进,使用同步的方法进行优化,来保证线程安全
	//但同时带来新的问题,每次调用获取实例都要进行同步,这就造成了不必要的同步开销
	public static synchronized Singleton getInstance(){
		if (instance==null) {
			instance=new Singleton3();
		}
		return instance;
	}
	
}
同样的可以使用上面main方法进行运行,会发现两者的hashcode值是一样的,使用同步方法可以解决懒汉模式的线程安全问题,但同时带来新问题,就是每次调用获取实例都要进行同步方法,也就是每次获取实例过程中,要先获取锁对象才可以访问实例,这就大大降低反应效率了,带来不必要同步开销,而大多数的情况是不需要用到同步的,一般不建议使用。

双重检查模式(double-checked-locking)DCL

public class Singleton {
	
	//声明了一个静态对象,初始状态是空对象
	private  static  Singleton instance;

	//私有构造函数,防止外部调用,确保实例的唯一性
	private Singleton() {

	}
	
	//使用同步代码块,优化懒汉模式的效率问题,但使用双重检查模式,有时候会失效。同样也不十分靠谱
	public static  Singleton getInstance(){
		if (instance==null) {
			synchronized (Singleton.class) {
				if (instance==null) {
					instance=new Singleton();
				}
			}
		}
		return instance;
	}
	
}

在获取实例中,进行两次判null,第一次判断是为了减少不必要的同步,第二次判断是为了初次调用的时候进行创建实例。双重检查模式的优点就是,解决懒汉模式的线程安全、减少不必要的同步方法等问题。但双重检查机制在某些情况会出现失效问题。同样的建议使用。

静态内部类单例

public class Singleton {

	//私有构造函数,防止外部调用,确保实例的唯一性
	private Singleton() {
	}
	
	/**
	 * 定义静态内部类
	 */
	public static class SingletonHolder{
		//在静态内部类中使用final修饰初始化实例,保证实例的唯一性,当会调用时才会加载到内存中
		private final static Singleton instance=new Singleton();
	}
	
	public static  Singleton getInstance(){
		return SingletonHolder.instance;
	}
	
}

在类加载Singleton时并不会初始化实例instance,而是在调用getInstance方法来实现实例化,原理是第一次调用时会加载内部类SingletonHolder,在内部类定义一个带final static修饰的instance变量(保证实例的唯一性),同时初始化Singleton实例,由java虚拟机来保证线程安全。推荐使用的静态内部类单例模式

枚举单例

public enum Singleton{
	
	INSTANCE;
}
枚举实现单例的写法十分简单,但可读性不是很高,默认枚举实例是线程安全的,并且任何情况都是单例。

使用场景

在一个系统中,要求一个类仅有一个实例对象。

  • 创建一个对象会消耗过多的资源,比如访问数据库和IO操作。
  • 一些工具类的对象

小结:单例模式的各种写法,可以理解为都是解决饿汉模式的缺陷而产生的链接方法,然而这些方法在解决了饿汉模式缺陷的同时,也给自己带来潜在的危险,比如说饿汉模式的缺陷是没有达到懒加载的效果,于是懒汉模式站出来了处理懒加载问题,就在此时懒汉模式以为万事大吉了,但是还是出现线程安全问题,于是懒汉模式马上进行自我优化,加入同步方法,解决线程安全问题,通过同步方法问题是解决了,后来还是发现不够理解,每次调用实例都要多出一步操作,必须拿到一把锁才可以打开,效率不是很高也带来资源的消耗;此时苦思冥想得到一个新的方案,就是双重判断,不是说我同步方法耗性能吗,引入同步代码块来实现,最后发现效果还是挺不错的,以后可以关闭问题了,但不好的消息又传来,你那单例也太不稳定了吧,怎么有时候没失效了…………已经无语了……直到静态内部类单例的出现彻底把各种窟窿弥补了,不管实现懒加载的效果,还保证了线程安全问题。最终一统江湖,以后的单例就按我的要求标准写……




感谢:

《大话设计模式》、《Android进阶之光》

猜你喜欢

转载自blog.csdn.net/hzw2017/article/details/80725929