持续创作,加速成长!这是我参与「掘金日新计划 · 6 月更文挑战」的第9天,点击查看活动详情
一、并发问题和MESI介绍
1、在并发编程中,一般需要解决原子性 、可见性 、有序性三个问题;
-
原子性
是指一个线程在操作时,不会被其他线程打断,直到他执行结束;
-
可见性
对于多线程或者多核CPU来回说,会有一个主存的概念,主存存放着共享变量,当某个线程修改了某缓存行的数据,被修改的数据要对其他线程可见;
-
有序性
就是指令的执行顺序,多线程环境下,发生指令重排可能导致程序执行上的错误;
MESI 协议,是用于解决内存的可见性问题的一种方式
硬件层面来讲,解决可见性可以通过加锁的方式解决;
-
总线锁
在cpu各级缓存跟主存中间通过BUS总线来进行与通信,当某一个cpu要对共享内存进行操作时,这个时候会对bus发一条LOCK的指令来阻断其他cpu跟主存的通信,这样其他cpu也没法访问别的地址的内存数据,显然这种方式开销很大,效率低下,目前一般不用;
-
缓存锁
缓存锁的核心机制就是缓存一致性协议,常见的就是MESI 协 议,
缓存锁的颗粒度是缓存行,当要修改一条volatile修饰的变量时,jvm会像cpu发一条lock指令,把数据所在的缓存行写会主存,如果其他cpu也读取了这个缓存行的数据,那么在感知到主存的数据变化的时候,会把已经拷贝到自己工作内存的缓存失效,重新从主存读取;
2、MESI协议
各个cpu之间通过MESI来保证缓存一致性;
- M: 被修改(Modified)
- 目标数据的缓存行只在本cpu的缓存有副本,且被修改,主存与缓存数据不一致;当缓存行数据写回主存,此时数据处于独占状态;
- E: 独占(Exclusive)
- 独占状态下的缓存行只在主存和当前cpu有数据;
- S: 共享(shared)
- 多个 CPU 中都有缓存,且与内存一致;
- I: 失效(Invalid)
- 有其他cpu修改了缓存行的数据,此时数据失效;
3、volatile原理
Java 的 volatile 关键字则可以保证共享变量间的内存可见性;
对于声明了volatile关键字的变量,在编译的时候会在变量前面加上lock前缀的指令;作用主要有三个;
- 将当前数据所在的缓存行刷回主存
- 可以让其他cpu缓存了这个数据的缓存行失效
- 当数据被重新写回主存组要经过总线通信,当其他cpu感知到自己缓存行的数据地址被修改,让自己的内存过期掉,等需要使用该数据时,在把它重新读到缓存;
- 禁止指令重排
- 有内存屏障的作用在多线程环境下,防止执行结果异常,可以禁止指令重排,cpu的重排是在单线程环境下,保证执行结果不变的情况下,对指令的执行顺序的优化;
4、JMM内存模型
简介:
- jmm内存模型是划分了主存和工作内存的概念;
- 主存:作为共享区,保存了java实例对象,所有变量都存在主存中;
- 工作内存:线程私有的,各个线程之间工作内存不可见,使用数据时,把主存中数据所在缓存行,复制加载到工作内存操作;
描述:
java内存模型是一种抽象概念,是描述的是一组规范;该规范描述了java实例的变量的访问操作方式,形象的说来就是JVM内部是以线程为单位运行的,jvm在创建线程的时候,会为它创建一个线程栈,可以理解成线程的工作内存,由于实例的变量都存在与主存中,因此每个线程如果要操作某些变量则需要到内存中把目标变量读取到自己的工作内存,不允许直接在主存中操作,在工作内存操作完成后在同步更新到主存;每个线程栈(工作内存)都会每个线程私有的,互相不可见;各个线程间通信要依赖于主存来完成;