名词介绍
- DCL: Double check lock
- 指令重排: java文件经过编译转化为字节码文件的时候,代码编译为一条条指令,一般来说是按照顺序执行的,但是比如说某条指令要从内存中拿数据,内存的速度要比cpu慢近乎百倍,这样cpu不可能等着该指令从内存中取到数据,所以就执行了后面的指令,这就是指令重排。
无论是否指令重排,都保证了最终一致性 - volatile: 多线程可见性、禁止指令重排(方式:指令屏障)
代码
@Data
public class Singleton {
private String s;
//这里为什么要用volatile:假如不使用volatile,可能导致指令重排,
//例如在setS的之前,定义s1,s1完成了半初始化(也就是给s1分配完内存空间,完成了初始化,此时s1 = null,还没来得及完成将“指令重排”赋值于s1)的时候发生指令重排
//接着执行,对INSTANCE完成了set值,此时有用到了INSTANCE的s发现s == null。错误
//虽然以上说法有很大偶然性,但是多线程千奇百怪,哈哈哈
private static volatile Singleton INSTANCE;
private Singleton() {}
private static Singleton getInstance() {
//DCL 双检查单例。在多线程的情况下
//如果不加锁,多个线程同时进入,判断INSTANCE为空后,获取多个实例,单例 的意义失效
//加锁后要注意加锁粒度,尽量减小加锁的粒度,因为会影响执行效率;
if (null == INSTANCE) {
synchronized (Singleton.class) {
//为什么要双检查:如果全部进入第一个null判断后,都在等待锁,如果不进行null判断,全部创建实例,单例失去意义
if (null == INSTANCE) {
INSTANCE = new Singleton();
//业务代码
String s1 = "指令重排";
INSTANCE.setS(s1);
}
}
}
return INSTANCE;
}
}