不要问我为什么要记单例模式 - -
本文主要介绍单例模式以及代码解析。

首先贴上代码:
饿汉式:运行时就创建好对象了。
public class EagerSingleton {
	
	private static final EagerSingleton instance =new EagerSingleton();
	private EagerSingleton() {}
	
	public static EagerSingleton getInstance() {
		return instance;
	}
}
简介:使用static修饰对象成为静态成员变量,初始化的时候便赋值,且是类变量,每个对象共用,static修饰方法可以通过类名直接调用(静态方法里可以直接调用静态变量,要调用普通变量必须实例化对象,因为静态是类成面的),private修饰构造函数且没有其他构造函数,那么子类想继承时只能实现父类的默认构造方法,用private修饰的使用范围是类本身,子类也不行,所以也就阻止了继承。关于final下面再和懒汉式一起讲。


懒汉式:第一次调用get才创建对象。
public class LazySingleton {

	private volatile static LazySingleton instance=null;
	
	private LazySingleton() {}
	
	public static LazySingleton getInstance() {
		if(instance==null) {
			synchronized(LazySingleton.class) {
				if(instance==null) {
					instance=new LazySingleton();
				}
			}
		}
		return instance;
	}
}

简介:关于private,上面说到了,阻止子类继承。static上面也提到了,类静态变量和静态方法。
第一次判断instance=null,判断实例有没有创建,如果有创建了就跳过。
synchronized加同步锁:代码段加锁,而且加的是class类锁,所有对象共用的锁,同一时间只有一个对象能运行代码块内容。
第二次判断instance=null,第一次判断其实是为了优化效率,不用每次都直接获取锁,第二次才是真正避免线程安全问题的,假设有两个线程同时通过了第一次instance=null判断,都在等待锁,其中一个线程获取了锁,另一个进入阻塞状态,当第一个运行完的时候实例已经创建了,这时候第二个线程获取到锁,如果不再判断一次那么就会创建出多一个实例,所以需要再一次判断instance=null
关于volatile下面来重点关注。


为什么懒汉式需要volatile?

上面懒汉式的代码在多线程下调用可能会报错,具体报错原因:

在语句

instance=new LazySingleton();
中并不是一个原子操作,在JVM中其实是3个操作:
1.给instance分配空间、
2.调用 LazySingleton 的构造函数来实例化、
3.将instance对象指向分配的内存空间(instance指向分配的内存空间后就不为null了);
在JVM中的及时编译存在指令重排序的优化,也就是说不能保证1,2,3执行的顺序,最终的执行顺序可能是 1-2-3 也可能是 1-3-2。如果是 1-3-2,则在 3 执行完毕、2 未执行之前,被线程二抢占了,这时 instance 已经是非 null 了(但却没有初始化),所以线程二会直接返回 instance,然后使用,然后顺理成章地报错。
通过添加volatile就可以解决这种报错,前面我们介绍过volatile能够保证有序性和可见性,所以volatile可以保证1、2、3的执行顺序,没执行玩1、2就肯定不会执行3,也就是没有执行完1、2instance一直为空。



为什么饿汉式需要final?【本人理解的不够深入,大致是和volatile差不多保障是有序性,防止正确实例化之前被别的线程引用了】

static+final修饰的变量是属于编译时常量,在类加载的准备阶段就通过在编译过程的特殊处理赋值了,而不是赋默认值null。
private static final EagerSingleton instance =new EagerSingleton();
static+final保障了类加载的准备阶段就被赋值,此时的值是对象,所以又会执行类的实例化。(比较混乱,个人理解有限)

final修饰引用变量规定了引用变量不能再指向其他对象,也就是引用不可变,可是单例模式本身属性和构造函数都是私有的,本来就不能改变了,所以其实final的用处和volatile是类似的:


Final 变量在并发当中,原理是通过禁止cpu的指令集重排序(重排序详解http://ifeve.com/java-memory-model-1/ http://ifeve.com/java-memory-model-2/),来保证对象的安全发布,防止对象引用被其他线程在对象被完全构造完成前拿到并使用。

(并发编程网原话:http://ifeve.com/java-memory-model/):
与前面介绍的锁和volatile相比较,对final域的读和写更像是普通的变量访问。对于final域,编译器和处理器要遵守两个重排序规则:
在构造函数内对一个final域的写入,与随后把这个被构造对象的引用赋值给一个引用变量,这两个操作之间不能重排序。
初次读一个包含final域的对象的引用,与随后初次读这个final域,这两个操作之间不能重排序。