两个线程对同一个变量修改(类似脏读),主线程对变量修改,变量对t1是不可见的
有序性问题简述CPU/JVM层面会优化指令执行顺序,引入了StoreBuffer异步通知的方式,导致线程中的执行顺序可能会发生改变(两个指令没有强关联关系会导致),在缓存获取数据以及通知其他cpu的过程中,导致后面的指令先执行了;
int a=0;
void test(){
a=1;
b=a+1;
assert(b==2);//false
}
//指令重排
b=a+1;
a=1;
如何解决
volatile通过缓存锁、总线锁(根据缓存一致性协议MESI)以及内存屏障解决的多线程中的可见性以及有序性问题的
可见性问题产生原因
因为执行速度:CPU > 内存 > 磁盘
所以为了提高CPU资源利用率(cpu等待内存或磁盘响应),通过下面三方面方式
1.CPU增加高速缓存,减少等待时间
2.操作系统中,增加进程、线程,通过CPU的时间片切换,提升cpu利用率
3.编译器,JVM的深度优化活性失效
最终因为高速缓存(三级缓存)引起问题
-
缓存一致性问题--因为线程的变量先从缓存再同步到主存,其他线程何时从主存同步时机未知,会导致不同线程中的缓存不一致,通过缓存锁或总线锁(根据缓存一致性协议MESI)调用汇编语言的lock,我们程序使用volatile来触发
-
伪共享问题--每个缓存里面都是由缓存行组成的,缓存系统中是以缓存行(cache line)为单位存储的。缓存行是2的整数幂个连续字节,一般为32-256个字节。最常见的缓存行大小是64个字节。当多线程修改互相独立的变量时,如果这些变量共享同一个缓存行,就会无意中影响彼此的性能,这就是伪共享,为了避免多个线程同时操作一个缓存行进行读写(使用对齐填充将空间都占满64,不让其他cpu来使用)空间换时间解决,使用注解@Contended
缓存一致性问题
MESI协议状态介绍
MESI中每个缓存行都有四个状态,分别是E(exclusive)、M(modified)、S(shared)、I(invalid)。
M:修改态,代表该缓存行中的内容被修改了,并且该缓存行只被缓存在该CPU中。
E:独享态,E代表该缓存行对应内存中的内容只被该CPU缓存,其他CPU没有缓存该缓存对应内存行中的内容。这个状态的缓存行中的内容和内存中的内容一致。该缓存可以在任何其他CPU读取该缓存对应内存中的内容时变成S状态。或者本地处理器写该缓存就会变成M状态。
S:共享态,该状态意味着数据不止存在本地CPU缓存中,还存在别的CPU的缓存中。这个状态的数据和内存中的数据是一致的。当有一个CPU修改该缓存行对应的内存的内容时会使该缓存行变成 I 状态。
I:无效态,代表该缓存行中的内容时无效的。
MESI工作原理
1、线程A从内存中将变量a加载到缓存中,并将变量a的状态改为E(独享),并通过总线嗅探机制对内存中变量a的操作进行嗅探
2、线程B读取变量a,总线嗅探机制会将线程A中的变量a的状态置为S(共享),并将变量a加载到线程B的缓存中,状态为S
3、线程A对变量a进行修改操作,此时线程A中的变量a会被置为M(修改)状态,而线程B中的变量a会被通知,改为I(无效)状态,此时线程B中的变量a做的任何修改都不会被写回内存中(高并发情况下可能出现两个CPU同时修改变量a,并同时向总线发出将各自的缓存行更改为M状态的情况,此时总线会采用相应的裁决机制进行裁决,将其中一个置为M状态,另一个置为I状态,且I状态的缓存行修改无效)
4、线程A将修改后的数据写回内存,并将变量a置为E(独占)状态
5、此时,线程B通过总线嗅探机制得知变量a已被修改,会重新去内存中加载变量a,同时线程A和线程B中的变量a都改为S状态
内存屏障
cpu不知道什么时候不允许优化,需要开发人员自行判断处理,使用内存屏障可以控制指令不允许优化。
-
读屏障lfence
-
写屏障sfence
-
全屏障mfence
JMM(java memory model)
Java内存模型的主要目标就是定义程序中各个变量的访问规则,即在虚拟机中将变量存储到内存和从内存中取出变量这样的细节。针对不同的操作系统与平台提供的
Happens-before规则(告诉你哪些场景不会存在可见性问题)
规则1.As-if-serial(程序顺序型规则):不管如何重排序,单线程执行结果一定不会发生变化
规则2.传递性规则:如果a在b之前,b在c之前,那么a在c之前
规则3.volatile变量规则:volatile变量修饰
规则4.监视器锁规则:如果线程1获得锁,并且执行完成后释放锁,线程2获取锁,他读取到的数据一定是线程1更改之后的
规则5.start规则:主线程设置变量在线程start之前,那么线程读取变量的值一定是设置后的值
规则6.join规则:join操作之后的执行,必须等待join线程执行结束后再执行



