知识点:什么是原子操作
简单的来说,原子操作(atomic)就是不可分割的操作,在计算机中,就是指不会因为线程调度被打断的操作。
比如,简单的赋值就是一个原子操作
例如m原先的值为0,那么对于这个操作,要么执行成功变成了6,要么执行失败变成了0,而不会出现诸如m=3这种中间状态–即使是在并发的线程中。
然而声明赋值就不是一个原子操作:
对于这个语句,至少有两个操作:
1、声明一个变量m
2、给m赋值为6
这样就会有一个中间状态:变量m已经被申明了但是还没有赋值
所以,这种状况在多线程中,由于线程执行的不确定性,如果两个线程都使用m,就可能倒是不稳定的结果出现
知识点:什么是指令重排
简单的来说:就是计算机为了提高执行效率,会做一些优化,在不影响最终结果的情况下,可能会对一些语句的执行顺序进行调整。
比如:
int a;//语句1
a=;//语句2
int b =; //语句3
int c =a+b;//语句4
正常来说,对于顺序结构,执行的顺序是自上到下
但是,由于指令重排,因为不影响最终结果,所以执行的顺序很可能编程3124,或者1234
由于3,4没有原子性问题,语句3,4可能会被拆分成原子操作,再重排。
也就是说,对于非原子性操作,在不影响结果的情况下,其拆分成的原子操作可能会重排执行顺序
所以在博客1中的懒汉式代码中 singleton = new Singleton()这句,这并非是一个原子操作,事实上在JVM中这句话大概做了下面3件事情
1.给singleton分配内存
2.调用Singleton的构造函数来初始化成员变量
3.将singleton对象指向分配的内存空间(执行到这一步,singleton才是非null的了)
但是在JVM的及时编译器中,存在指令重拍的优化,也就是说,第二步和第三步的顺序是无法保证的
而导致程序出错
所以我们拥有了一个最终版本
3.4终极版本:volatile
public class Singleton {
private static volatile Singleton singleton;
private Singleton() {
}
public static Singleton getsingleton() {
if (singleton == null) {
synchronized (Singleton.class) {
if (singleton == null) {
singleton = new Singleton();
}
}
}
return singleton;
}
}
volatile关键字的作用是禁止指令重排,把singleton声明为volatile之后,对它的写操作就会有一个内存屏障,这样,在它完成赋值之前,就不会调用读的操作了