为了提高程序运行的性能,现代CPU在很多方面对程序进行了优化。: 例如: CPU高速缓存。 尽可能地避免处理器访问主内存的时间开销,处理器大多会利用缓 存(cache)以提高性能。
CPU在读取数据时,先在L1中寻找,再从L2寻找,再从L3寻找,然后是内存,再后是外存储器。
多CPU读取同样的数据进行缓存,进行不同运算之后,最终写入主内存以哪个CPU为准?
在这种高速缓存回写的场景下,有一个缓存一致性协议, 多数CPU厂商对它进行了实现。
MESI协议
,它规定每条缓存有个状态位,同时定义了下面四个状态:
多处理器时,单个CPU对缓存中数据进行了改动,需要通知给其他CPU 这意味着,CPU处理要控制自己的读写操作,还要监听其他CPU发出的通知,从而保证最终 一致。
缓存中的数据与主内存的数据并不是实时同步的,各CPU (或CPU核心)间缓存的数据也不是 实时同步。在同一个时间点,各CPU所看到同一内存地址的数据的值可能是不一致的。
内存模型描述程序的可能行为。
Java虚拟机规范中试图定义一种Java内存模型来屏蔽掉各种硬件和操作系统的内存访问差异
,规定
以实现让Java程序在各种平台下都能达到一致性的内存访问效果。
Java编程语言内存模型 通过检查执行跟踪中的每个读操作,并根据某些规则检查该读操作观察到的写操作是否有效来工作。
只要程序的所有执行产生的结果都可以由内存模型预测。具体的实现者任意实现,包括操作的重新排序和删除不必要的同步。
内存模型决定了在程序的每个点上可以读取什么值
可以在线程之间共享的内存称为共享内存或堆内存
所有实例字段、静态字段和数组元素都存储在堆内存中
如果至少有一个访问是写的,那么对同一个变量的两次访问(读或写)是冲突的。
各个变量的访问规则
即在虚拟机中将变量存储到内存和从内存中取出变量值这样的底层细节此处的变量
包括了实例域,静态域和构成数组对象的元素,但不包括局部变量与方法参数,因为后者是线程私有的,不存在竞争
为了获得比较好的执行效率,JMM并没有限制执行引擎使用处理器的特定寄存器或缓存来和主内存进行交互,也没有限制即时编译器调整代码执行顺序这类权限。
JMM规定
变量的主内存副本拷贝
(线程所访问对象的引用或者对象中某个在线程访问到的字段,不会是整个对象的拷贝!)
线程对变量的所有操作(读,赋值等)都必须在工作内存中进行,不能直接读写主内存中的变量
volatile变量依然有工作内存的拷贝,只是他特殊的操作顺序性规定,看起来如同直接在主内存读写
不同线程之间无法直接访问对方工作内存中的变量,线程间变量值的传递均要通过主内存
JVM模型与JMM不是同一层次的内存划分,基本是没有关系的,硬要对应起来,从变量,内存,工作内存的定义来看
从更底层的层次来说
write要写的变量以及要写的值。 read要读的变量以及可见的写入值(由此,我们可以确定可见的值)。 lock要锁定的管程(监视器monitor)。 unlock要解锁的管程。 外部操作(socket等等…) 启动和终止
如果一个程序没有数据竞争,那么程序的所有执行看起来都是顺序一致的
本规范只涉及线程间的操作;
一个变量如何从主内存拷贝到工作内存,从工作内存同步回主内存的实现细节
JMM 本身已经定义实现了以下8种操作来完成,且都具备原子性
复制
到工作内存
就要顺序执行read和load
同步
回主内存
就要顺序地执行store和write操作
JMM只要求上述两个操作必须按序执行
,而没有保证连续执行
也就是说read/load之间、store/write之间可以插入其它指令
如对主内存中的变量a,b访问时,一种可能出现的顺序是read a->readb->loadb->load a
JMM规定执行上述八种基础操作时必须满足如下
◆ 对于监视器 m 的解锁与所有后续操作对于 m 的加锁 同步
(之前的操作保持可见)
◆对 volatile变量v的写入,与所有其他线程后续对v的读同步
◆ 启动
线程的操作与线程中的第一个操作同步
◆ 对于每个属性写入默认值(0, false, null)与每个线程对其进行的操作同步
◆ 线程 T1的最后操作与线程T2发现线程T1已经结束同步。( isAlive ,join可以判断线程是否终结)
◆ 如果线程 T1中断了T2,那么线程T1的中断操作与其他所有线程发现T2被中断了同步通过抛出InterruptedException异常,或者调用Thread.interrupted或Thread.isInterrupted