先来看java内存模型图:
线程的共享变量都存储在主内存中,每个线程都有自己的工作内存,线程操作数据会先将主内存的数据加载到自己的工作空间,然后进行数据操作,最后写回到主内存。
多线程情况下,线程1拿到data=0,然后进行自增操作。在线程1没有将数据刷回主内存的情况下,线程2拿到了脏数据,然后进行自增操作。最后都刷回了主内存,结果是data只进行了一次自增操作。
在加上volatile关键字后,可以保证可见性。
线程1修改数据后,会立刻将数据刷回主内存,二线程2的数据也会失效,强制重新从主内存加载数据。这样就保证线程1和线程2看到的变量是同一个。
volatile保证可见性是通过lock前缀指令触发MESI缓存一致性新协议实现的:
MESI四个状态:
M(modified):代表该缓存行中的数据有效,数据被修改了,缓存行数据和内存数据不一致,会被写入内存。
E(exclusive):代表该缓存行对应内存中的内容只被该CPU缓存,其他CPU没有缓存该缓存对应内存行中的内容。缓存行中的内容和内存中的内容一致。该缓存可以在任何其他CPU读取该缓存对应内存中的内容时变成S状态。或者本地处理器写该缓存就会变成M状态。
S(shared):代表数据和内存中的数据是一致的,其他cpu也存有。当有一个CPU修改该缓存行对应的内存的内容时会使该缓存行变成 I 状态。
I(invalid):代表该缓存行中的内容无效。
2.有序性volatile有序性是通过内存屏障实现的。
有时候为了提高效率,cpu会进行指令重排。一个指令的执行被分成:取指、译码、访存、执行、写回、等若干个阶段。流水线是并行的,多个指令可以在同时在流水线中,同时执行。
cpu是顺序从内存取出指令,然后顺序地放入流水线。
重排规则(happens-before原则):
1.程序次序规则:一个线程内,按照代码书写顺序操作。
2.锁定规则:一个unlock操作执行先于lock操作。
3.volatile变量规则:一个volatile变量的写操作先于读操作执行。
4.传递规则:A——>B——>C,那么A先于C执行。
5.线程启动规则:start()方法先于此线程的每个动作。
6.线程中断规则:interrupt()方法先于被中断线程的代码检测到中断事件发生。
7.线程终结规则:线程中所有操作都先于线程的终止检测。
8.对象终结规则:一个对象的初始化完成先于他的finalize()方法的开始。
乱序:因为指令与指令间会相互影响,就可能造成顺序流入,乱序执行。
为了抑制乱序,就需要内存屏障(一个CPU的多个操作的顺序):
读屏障:所有在读内存屏障之前的加载操作将在读内存屏障之后的加载操作之前发生。
写屏障:可以保证在看到写内存屏障之后的写入数据之前先看到写内存屏障之前的写入数据。
通用屏障:通用内存屏障之前的加载、存储操作都将在通用内存屏障之后的加载、存储操作之前发生。
编译屏障:编译屏障只是告诉编译器,不要对当前代码进行过度的优化,保证生成的汇编代码的次序与当前高级语言的次序保持一致。编译屏障对CPU执行时产生的重排序没有任何作用。



