定义
确保某个类只有一个实例,而且自行实例化并向整个系统提供这个实例。
创建方式
/**
* 饿汉式
*
* 类加载时,实例就跟创建初始化了,所以是线程安全 (类加载的过程就是线程安全的)
* 不支持懒加载
*/
public class SingleHunger {
private static SingleHunger instance = new SingleHunger();
private SingleHunger() {
}
public static SingleHunger getInstance() {
return instance;
}
}
/**
* 懒汉式
*
* 线程安全
* 因为在 getInstance方法加了个锁,那么每次在使用这个单例时,我们都会调用getInstance方法,
* 那么也就每次都需要加锁、释放锁了。 如果单例使用比较频繁的话,这种方式就不是很好了
*/
public class SingleLazy {
private SingleLazy instance;
private SingleLazy(){
}
private synchronized SingleLazy getInstance(){
if (instance == null)
instance = new SingleLazy();
return instance;
}
}
/**
* 双重校验
*/
public class SingleDoubleCheck {
private volatile SingleDoubleCheck instance;
private SingleDoubleCheck(){
}
private SingleDoubleCheck getInstance(){
if (instance == null){
synchronized (SingleDoubleCheck.class){
if (instance == null){
instance = new SingleDoubleCheck();
}
}
}
return instance;
}
}
双重锁校验为什么instance要使用关键字`volatile`
因为 `instance = new Single()` 这句赋值代码不是原子操作,这句代码包含以下3个步骤
1. 给 `new Single()`所即将创建的对象分配内存空间
2. 初始化对象 ,即执行`new Single()`构造方法
3. 把创建对象的内存空间地址赋值给`instance`
编译器指令重排可能会把2、3步的顺序颠倒,这样的话,`Single`对象还没真正初始化,但内存地址已经被赋值给了变量`instance`,
这样其他线程判断`instance`不为空,就直接拿去使用了,但instance其实并没有真正的被初始化,也就出现了问题。
所以使用关键字`volatile`来修饰`instance`禁止指令重排。
`volatile` 解决有序性:
采用部分禁止指令重排。
对于volatile修饰的变量执行写操作,禁止位于其前面的读写操作与其进行重排序;
对于volatile修饰的变量执行读操作,禁止位于其后面的读写操作与其进行重排序
这样,步骤3是对`instance`进行写操作,根据`volatile`指令禁止重排规则,
位于其前面的读写操作禁止与其进行重排序,那么步骤2也就不能和步骤3指令重排序了。
/**
* 静态内部类
*/
public class SingleStatic {
private SingleStatic() {
}
public static class SingleHandler {
private static final SingleStatic instance = new SingleStatic();
}
public static SingleStatic getInstance() {
return SingleHandler.instance;
}
}
为什么这种方式创建的单例是支持懒加载的?
答:静态变量的初始化是在类加载时,类是在用到它的时候才被加载的。
所以instance真正初始化 时机是在调用getInstance方法时。
而且静态变量只会初始化一次,所以我们后续调用getInstance得到的是同一个实例。
为什么是线程安全的?
答:因为getInstance整个流程都是在类加载过程中完成的,而类的加载机制是线程安全的。
不用静态内部类可以吗?
答:如果是普通内部类的话,是不可以声明静态变量的。
如果instance不是静态的,那么就无法在类加载过程中初始化,也就无法保证线程安全了。