缓存带来的可见性问题
在现代的CPU架构中,CPU并不是直接和内存打交道的,因为CPU对内存读取和写入的速度很慢,因此在CPU的基础上加了多级缓存,用来加快读取和写入的速度。
为了实现缓存之间的相互同步,CPU会遵循缓存一致性协议,保证CPU缓存之间不会出现不同步问题,因此不会有内存可见性问题
但是缓存一致性协议对性能的损耗较大,因此在L1缓存的基础上又增加了Store Buffer,Load Buffer等Buffer。需要注意的是L1,L2,L3和主内存之间因为缓存一致性的保证是同步的。但是Store Buffer,Load Buffer等和L1之间却是异步的,所以会出现内存可见性问题
例如往内存中写入一个变量,这个变量会保存在Store Buffer里面,稍后才会异步写到L1中,同时同步写入主内存。
我们把这个模型抽象一下就得到了JMM(Java内存模型),后续我们分析并发问题都是基于这个模型
线程切换带来的原子性问题
CPU在执行任务时,并不是执行完一个线程再执行另一个线程,而是在执行期间会发生线程切换。这样宏观看起来就是多个线程在同时运行。
假设sum=0,执行如下代码
sum++
sum++其实是如下3步操作
读取sum=0
计算sum+=1
将1赋值给sum
线程切换就会带来原子性问题。2个线程同时执行sum++时,执行顺序有可能如下所示
时间 | 线程A | 线程B |
---|---|---|
t1 | 读取sum=0 | |
t2 | 读取sum=0 | |
t3 | 计算sum+1=1 | |
t4 | 计算sum+1=1 | |
t5 | 将1赋值给sum | |
t6 | 将1赋值给sum |
sum被2个线程加了2次,但是值却只增加了1
编译优化带来的有序性问题
当我们想泡茶的时候可能会经历如下几个步骤烧水壶->烧开水->洗茶壶->洗茶杯->拿茶叶->泡茶。
为了更快的获得结果,我们可以在烧开水的时候去洗茶壶和洗茶杯
程序也是同样的道理,假如说有个代码段是执行IO操作时,如果后面的代码段对IO操作没有依赖关系是,我们完全可以在IO操作这段时间执行后面的代码段,此时代码就被执行了重排序
编译器和CPU都会对代码进行重排序。用单例模式演示一下重排序带来的问题
public class Singleton {
private static Singleton uniqueInstance;
private Singleton() {}
public static Singleton getInstance() {
if (uniqueInstance == null) {
synchronized (Singleton.class) {
if (uniqueInstance == null) {
uniqueInstance = new Singleton();
}
}
}
return uniqueInstance;
}
}
uniqueInstance = new Singleton()可以分解为如下三行伪代码
memory = allocate(); // 1:分配对象的内存空间
ctorInstance(memory); // 2:初始化对象
uniqueInstance = memory;// 3:设置uniqueInstance指向刚分配的内存地址
3行伪代码中的2和3之间,可能会被重排序,重排序后执行时序如下
memory = allocate(); // 1:分配对象的内存空间
uniqueInstance = memory;// 3:设置uniqueInstance指向刚分配的内存地址
// 注意,此时对象还没有被初始化
ctorInstance(memory); // 2:初始化对象
多个线程访问时可能出现如下情况
时间 | 线程A | 线程B |
---|---|---|
t1 | A1:分配对象的内存空间 | |
t2 | A3:设置uniqueinstance指向内存空间 | |
t3 | B1:判断uniqueinstance是否为空 | |
t4 | B2:由于uniqueinstace不为null,线程B间访问uniqueinstance引用的对象 | |
t5 | A2:初始化对象 | |
t6 | A4:访问instace引用的对象 |
这样会导致线程B访问到一个还未初始化的对象,此时可以用volatile来修饰Singleton,这样3行伪代码中的2和3之间的重排序,在多线程环境中将会被禁止
public class Singleton {
private volatile static Singleton uniqueInstance;
private Singleton() {}
public static Singleton getInstance() {
if (uniqueInstance == null) {
synchronized (Singleton.class) {
if (uniqueInstance == null) {
uniqueInstance = new Singleton();
}
}
}
return uniqueInstance;
}
}
参考博客
[1]