CPU Cache 和 Java 内存模型
volatile 关键字只能修饰类变量和实例变量,对于方法参数、局部变量以及实例常量、类常量都不能进行修饰。
CPU Cache 模型
三级缓存 CPU Cache 又是由很多个 Cache Line 构成,Cache Line 可以认为是 CPU Cache 中的最小缓存单位,大小 64 字节。
CPU 缓存一致性问题
缓存不一致性问题的主流解决方法:
- 总线加锁
- 缓存一致性协议
MESI 协议: 当 CPU 在操作 Cache 中的数据时,如果发现该变量是一个共享变量,也就是说在其他的 CPU Cache 中也存在一个副本,那么进行如下操作:
- 读取操作,不做任何处理,只是将 Cache 中的数据读取到寄存器
- 写入操作,发出信号通知其他 CPU 将该变量的 Cache line 置为无效状态,其他 CPU 在进行该变量读取的时候不得不到主内存中再次获取
Java 内存模型
Java 的内存模型决定了一个线程对共享变量的写入何时对其他线程可见,Java 内存模型定义了线程和主内存之间的抽象关系
- 共享变量存储于主内存中,每个线程都可以访问
- 每个线程都有私有的工作内存或者称为本地内存
- 工作内存只存储该线程对共享变量的副本
- 线程不能直接操作主内存,只有先操作了工作内存之后才能写入主内存
- 工作内存和 Java 内存模型一样也是一个抽象的概念,它其实并不存在,它涵盖了缓存、寄存器、编译器优化以及硬件等。
并发编程的特性
- 原子性
- 有序性
- 可见性
原子性
在一次的操作或者多次操作中,要么所有的操作全部都得到了执行并且不会受到任何因素的干扰而中断,要么所有的操作都不执行
- 多个原子性的操作在一起就不再是原子性操作了
- 简单的读取与赋值操作是原子性的,将一个变量赋给另一个变量的操作不是原子性的
- Java 内存模型(JMM)只保证了基本读取和赋值的原子性操作,其他的均不保证
synchronized 和 JUC 中的 Lock 可以使代码具有原子性,volatile 不行。想要 int 等类型的自增操作具备原子性,可以使用 JUC 包下的原子封装类 java.util.concurrent.atomic.*
可见性
当一个线程对共享变量进行了修改,那么另外的线程可以立即看到修改后的最新值。三种方式保证可见性:
synchronized关键字 确保同一时间只有一个线程能获得锁然后执行同步方法,确保在锁释放之前,会将对变量的修改刷新到主内存中JUC中的显式锁Lock确保同一时间只有一个线程能获得锁然后执行同步方法,确保在锁释放之前,会将对变量的修改刷新到主内存中volatile关键字 当一个变量被volatile关键字修饰时,对于共享资源的读操作会直接在主内存中进行(当然也会缓存到工作内存中,当其他线程对该共享资源进行了修改,则会导致当前线程在工作内存中的共享资源失效,所以必须从主内存中再次获取),对于共享资源的写操作当然要修改工作内存,但是修改结束后会立刻将其刷新到主内存中。
有序性
程序代码在执行过程中的先后顺序 指令重排序 Happens-before 原则:
- 程序次序规则
- 锁定规则
volatile变量规则- 传递规则
- 线程启动规则
- 线程中断规则
- 线程终结规则
- 对象终结规则


