原子性其实在数据库中也有体现,拿转账为例,张三给李四转1块钱,业务逻辑为张三减1块钱,李四加一块钱,这些必须同时成功。此处略去一万字。
当CPU访问数据的时候,其实是有多级缓存的,如下图所示(深入理解计算机系统 原书第三版 ),当多个CPU去访问一个数据的时候,他会把主存中的数据先缓存到高速缓存里,然后在执行操作,那么此时如果两个CPU同时执行a=a+1操作,如果没有一定的同步方法,那么就会出现修改丢失的问题,因此可见性就是保证当一个CPU或者线程对某一个共享变量修改后,对其他CPU或者线程可见。
Java内存模型规定了所有的变量都存储在主内存中,每条线程还有自己的工作内存,线程的工作内存中保存了该线程中是用到的变量的主内存副本拷贝,线程对变量的所有操作都必须在工作内存中进行,而不能直接读写主内存。不同的线程之间也无法直接访问对方工作内存中的变量,线程间变量的传递均需要自己的工作内存和主存之间进行数据同步进行。 其实还是和操作系统中一样的问题。需要实现当一个线程对某个变量修改后对其他线程可见。
除了引入了时间片以外,由于处理器优化和指令重排等,CPU还可能对输入代码进行乱序执行,比如load->add->save 有可能被优化成load->save->add 。这就是有序性问题。
如下面代码所示,逻辑为先初始化context,然后在做一些事情
//线程1
boolean init = false;(1)
String context = initialContext();(2)
init = true;(3)
//线程2
while (!init){ (4)
sleep(1L);
}
context.doSomeThing();(5)
但是如果出现了重排序的情况,线程1先执行(1)(3),那么此时线程2执行(4),在context还没有初始化的情况下,就进行了操作,这是有问题的。
层面 | 体现 |
---|---|
java源码 | volatile int i 关键字 |
ByteCode字节码 | ACC_VOLATILE |
JVM虚拟机 | JVM内存屏障 |
操作系统 | MESI 原语支持 总线锁 |
其实在java源码中看,volatile其实就是一个关键字
public class Start {
private static volatile Start ins = null;
public static Start getInstance(){
if (ins == null){
synchronized (Start.class){
if (ins == null){
ins = new Start();
}
}
}
return ins;
}
public static void main(String[] args) {
System.out.println(1);
}
}
从字节码角度看,其实就是在一个变量上打一个标记。
StoreStoreBarier
volatile 写操作
StoreLoadBarier
LoadLoadBarier
volatile 读操作
LoadStoreBarier
缓存一致性协议MESI。
https://www.bilibili.com/video/BV1QT4y1L7G9?p=5