这些年,我们的cpu、内存和i/o设备都在不断迭代,不断朝着更快的方向努力。但是,在这个快速发展的过程中,有一个核心矛盾一直存在,就是这三者的速度。
为了合理利用cpu的高性能,平衡这三者的速度差异,计算机系结构、操作系统、编译程序都做出了贡献,主要体现为:
在多核cpu时代,每个cpu都有自己的缓存,cpu1对于缓存的操作对于cpu2是不可见的,所以当cpu2对某一个值处理时,是不知道别的cpu对它的更改的,所以会有可见性问题
java并发程序是基于多线程的,多线程自然就涉及任务切换,比如一条 count+1=1, 至少需要三条cpu指令
cpu保证的是cpu指令是原子的,但是高级语言的指令不会是原子操作。一条高级语言的操作往往对于多条cpu指令,如果想解决这种原子性的问题,大多要使用🔒。
通过一个双重检查创建单例对象来说明这个编译优化带来的问题:
public class Singletion{
static Singleton instance;
static Singletion getInstance(){
if (instance == null ){
synchronized(Singleton.class){
if(instance == null)
instance = new Singletion();
}
}
}
}
我们理想的情况是:
but:这里问题出现在了 newSingleton上,new Singleton会出现先释放引用,然后再分配内存的指令重排序,如果刚好在这个时候,内存还没来的分配好,就发生了线程切换,线程B 判断instance == null 为false,就返回了,但是真正去获取成员变量的时候,就会发生空指针,因为成员变量内存还没有分配好。
java提供了很多关键字(sync、final、volatile)来按需禁用缓存、按需禁用编译优化。
volatile、sync这些解决可见性本质上就是通过内存屏障保证对于工作内存的修改或者读取都直接作用于主内存
volatile并不具备原子性,只是在32位机器上对于Long类型的读写保证原子性
要具备原子性,还是要有锁,要有临界区的概念,要让一组操作一起。sync里面的操作都是具有原子性的。
volatile、sync都是通过内存屏障来解决有序性的问题