1.直接实例化对象并返回
public class Single {
//直接生成
private static final Single instance1 = new Single();
public static Single getInstance1() {
return instance1;
}
}
2.静态内部类
public class Single {
//静态内部类
public static final class Holder {
private static final Single instance2 = new Single();
}
public static Single getInstance2() {
return Holder.instance2;
}
}
3.双重检查锁
public class Single {
private static volatile Single instance3 = null;
public static Single getInstance3() {
if (instance3 == null) {
synchronized (Single.class) {
if (instance3 == null) {
instance3 = new Single();
}
}
}
return instance3;
}
}
问题1.为什么使用volatile关键字
线程的三要素:原子性,可见性,有序性
volatile的实现原理:内存屏障
当CPU写数据时,如果发现一个变量在其他CPU中存有副本,那么会发出信号量通知其他CPU将副本对应的缓存行置为无效状态,当其他CPU读取到变量副本的时候,会发现该缓存行是无效的,会从主存中重新读取变量
volatile的三个作用(保证可见性+有序性)
1.解决多核CPU高速缓存导致的变量不同步,保证线程的可见性
CPU的高速缓存速度远远快于主存(物理内存)
CPU在读取一个变量时,会把数据先读到缓存,这样下次再访问同一个数据时直接从缓存读取,提高了读取性能,多核CPU则有多个这样的缓存。
这样引发的问题:当CPU1修改了这个变量(比如把a的值从1修改为2),但是其他CPU(比如CPU2)在修改之前就已经将a=1读取到自己的缓存了,当CPU2再次读取自己的数据的时候,会优先从自己的缓存区读取,此时读到的还是1,实际上a=2了
因此volatile可以保证线程的可见性。
线程可见性:多个线程在访问同一个变量时,如果其中一个线程修改了变量的值,那么其他线程应能立即看到修改的后的值
2.解决指令重排序的问题
单线程执行下:CPU保证了指令顺序不一定一致,但结果一定一致
多线程会引发出错,比如:
New 一个对象有三个步骤
1 开辟内存
2 对象赋值地址
3 初始化
如果发生指令重排,步骤为1,3,2,当进行到1,3的时候发生异常,此时的对象并不是一个完整的对象,但是此对象不为null。
因此,需要禁止指令重排。
3.不保证原子性
原子性:一个或多个操作,要么全部连续执行且不被任何元素中断,要么都不执行
问题2.为什么使用两重检查(if (instance3 == null) )
一重判断:如果不为空,直接返回 不用进锁,节约时间
二重:还需要判断 instance 是否为空,是为了防止在多线程并发的情况下,会实例化多个对象
例如:线程 a 和线程 b 同时调用 getInstance 方法,假如同时判断 instance 都为空,这时会同时进行抢锁。
假如线程 a 先抢到锁,开始执行 synchronized 关键字包含的代码,此时线程 b 处于等待状态。
线程 a 创建完新实例了,释放锁了,此时线程 b 拿到锁,进入 synchronized 关键字包含的代码,
如果没有再判断一次 instance 是否为空,则可能会重复创建实例。