概念:
单例模式(Singleton Pattern):确保某一个类只有一个实例,而且自行实例化并向整个系统提供这个实例,这个类称为单例类,他提供了全局访问的方法。单例模式是一种对象创建型模式。

Spring依赖注入Bean实例默认都是单例的,所以我们这一章回顾一下单例模式。

传统创建类代码

package pattern;

public class Case_1 {
	public static void main(String args[]){
		Singleten s1 = new Singleten();
		Singleten s2 = new Singleten();
	}
}

class Singleten{
}

在上段代码中,我们每次new Singleten(),都会创建一个Singleten实例,显然不符合一个类只有一个实例的要求。所以作出如下修改:

/**
 * 单例模式实例
 */
public class Case_1{
	public static void main(String args[]){
		Singleten s = Singleten.getInstance();
	}
}


/**
 * 描述:单例类(饿汉模式)
 * @author yangfan
 *  */
class Singleten{
	//stap2.自行对外提供实例
	private static final Singleten singleten = new Singleten();
	//stap1.构造函数私有化
	private Singleten(){
		
	}
	//stap3.提供外界可以获得该实例的方法
	public static Singleten getInstance(){
		return singleten;
	}
	
}

单例模式的写法有很多种,上述代码是一个简单的饿汉模式的实现代码,实现步骤总共有三步:

  • 构造函数私有化
  • 自行对外提供实例
  • 提供外界可获得该实例的方法

与饿汉模式相对应的还有懒汉模式,懒汉模式有延迟加载的意思,具体代码如下:

/**
	 * 懒汉模式
	 */
	private static Singleten singleten = new Singleten();
	private Singleten(){};
	public static Singleten getInstance(){
		//1.判断对象是否创建
		if(null==singleten){
			singleten = new Singleten();
			
		}
		return singleten;
	}

如果创建单例对象会消耗大量资源,那么懒汉模式的延迟创建是个不错的选择
但是
懒汉模式有个明显的问题,就是没有考虑到线程的安全问题,在多线程的并发情况下,会并发调用getInstance()方法,从而导致系统同时创建多个单例类实例,这显然不符合要求。
我们可以通过给getInstance()添加锁来解决该问题:

/**
	 * 懒汉模式(添加Synchronized锁)
	 */
	private static Singleten singleten = new Singleten();
	private Singleten(){};
	public static synchronized Singleten getInstance(){
		//1.判断对象是否创建
		if(null==singleten){
			singleten = new Singleten();
			
		}
		return singleten;
	}

添加synchronized锁虽然可以保证线程安全,但是每次调用个体Instance()方法时,都会有加锁和解锁操作,其次synchronized锁是添加在方法上,锁的范围过大,而单例类是全局唯一的。
因此,需要使用“双重校验锁”进行优化:

/**
	 * 双重校验锁(指令重排问题)
	 * 
	 */
	private static Singleten singleten = new Singleten();
	private Singleten(){};
	public static Singleten getInstance(){
		//第一次校验
		if(singleten == null){
			synchronized(Singleten.class){
				//第二次校验
				if(singleten == null){
					singleten =new Singleten();
				}
			}
		}
		return singleten;
	}

以上双重校验锁或出现指令重排的问题:
指令重排:指JVM为了优化指令,提高程序运行效率,在不影响单线程程序执行结果的前提下,尽可能的提高并行度
singleten = new Singleten()可以抽象为以下JVM指令:

//1.分配对象的内存空间
memory = allocate();
//2.初始化对象
ctorInstance(memory);
//3.设置instance指向刚分配的内存地址
singleten = memory;

上述操作2依赖于操作1,但是操作3并不依赖于操作2,所以JVM是可以针对他们进行指令的优化重新排序
排序后步骤如下

//1.分配对象的内存空间
memory =allocate();
//2.instance指向刚分配的地址,此时对象还没有初始化
singleten = memory;
//3.初始化对象
ctorInstance(memory);

为了解决指令重排问题,可以使用volatile关键字修饰singleten字段。volatile关键字的一个语义就是禁止指令的重新排序优化,阻止JVM指令重排,修改如下:

/**
	 * 双重校验锁(volatile解决指令重排问题)
	 */
	private static volatile Singleten singleten = new Singleten();
	private Singleten(){};
	public static Singleten getInstance(){
		//第一次校验
		if(singleten == null){
			synchronized(Singleten.class){
				//第二次校验
				if(singleten == null){
					singleten =new Singleten();
				}
			}
		}
		return singleten;
	}

当然,除了双重校验锁的方法,还有一种比较好的单例模式写法:
静态内部类的单例模式

/**
	 * 静态内部类的单例模式(推荐写法)
	 */
	//2.私有的静态内部类,类加载器负责加锁
	private static class SingletenHolder{
		private static Singleten singleten = new Singleten();
	}
	//1.构造方法私有化
	private Singleten(){};
	//3.自行对外提高实例
	public static Singleten getInstance(){
		return SingletenHolder.singleten;
	}