标签:tor 通过 内存地址 如何 硬件 问题 thread 生成 memory
在主线程对变量的修改对于线程读取该变量是不可见的,线程读取的是本地内存缓存的变量值。
使用volatile变量,可以解决共享数据在多线程环境下可见性的问题。
使用volatile关键字修饰变量后,在生成汇编指令的时候,会生成一个lock指令。
思考lock汇编指令来保证可见性问题?
在多线程环境下,读和写发生在不同的线程中,可能会出现:读线程不能及时的读取到其他线程写入的最新的值,这就是所谓的可见性问题。
CPU/内存/IO设备之间存在读取速度的差距和存储大小的差异
为了减小各类型设备之间的读取效率差异,增加CPU的处理效率
因为高速缓存的存在,会导致一个缓存一致性问题。
总线锁:在多核CPU下,当其中一个处理器要对共享内存进行操作的时候,在总线上发出一个LOCK#信号,这个信号使得其他CPU无法通过总线来访问到共享内存中的数据,总线锁定把CPU和内存之间的通信锁住了,在锁定期间,其他处理器不能操作其他内存地址的数据,所以总线锁的开销比较大,这种机制显然是不合理的。
优化方法:最好的办法是控制控制锁的保护粒度,我们只需要保证对于被多个CPU缓存的同一份数据是一致的就行。在P6架构的CPU后,引入了缓存锁,如果当前数据已经被CPU缓存了,并且是要写回到主内存中的,就可以采用缓存锁来解决问题。
所谓的缓存锁,就是指内存区域如果被缓存在处理器的缓存行中,并且在Lock期间被锁定,那么当它执行锁操作回写到内存时,不再总线上加锁,而是修改内部的内存地址,基于缓存一致性协议来保证操作的原子性。
X86架构下:引入了缓存锁。由高速缓存写入主内存的时候,不会在总线上加锁。只有数据写入高速缓存的时候,才会加上缓存锁。在数据写入高速缓存的时候,才可以使用缓存锁,缓存锁具有一定的机制(MESI),保证多个高速缓存之间数据的一致性。
总线锁和缓存锁怎么选择,取决于很多因素,比如CPU是否支持、以及存在无法缓存的数据时(比较大或者快约多个缓存行的数据),必然还是会使用总线锁。
MSI MESI MOSI
为了达到数据访问的一致,需要各个处理器在访问缓存时遵循一些协议,在读写时根据协议来操作,常见的协议有MSI,MESI,MOSI等。最常见的就是MESI协议。
MESI表示缓存行的四种状态,分别是
1.M(Modify) 表示共享数据只缓存在当前CPU缓存中,并且是被修改状态,也就是缓存的数据和主内存中的数据不一致
2.E(Exclusive) 表示缓存的独占状态,数据只缓存在当前CPU缓存中,并且没有被修改
3.S(Shared) 表示数据可能被多个CPU缓存,并且各个缓存中的数据和主内存数据一致
4.I(Invalid) 表示缓存已经失效
Store Bufferes是一个写的缓冲,对于上述描述的情况,CPU0可以先把写入的操作先存储到StoreBufferes中,Store Bufferes中的指令再按照缓存一致性协议去发起其他CPU缓存行的失效。而同步来说CPU0可以不用等到Acknowledgement,继续往下执行其他指令,直到收到CPU0收到Acknowledgement再更新到缓存,再从缓存同步到主内存。
X86的memory barrier指令包括lfence(读屏障) sfence(写屏障) mfence(全屏障)
使用volatile提供对内存屏障指令的调用。
JMM定义了共享内存中多线程程序读写操作的行为规范:在虚拟机中把共享变量存储到内存以及从内存中取出共享变量的底层实现细节。通过这些规则来规范对内存的读写操作从而保证指令的正确性,它解决了CPU多级缓存、处理器优化、指令重排序导致的内存访问问题,保证了并发场景下的可见性.
需要注意的是,JMM并没有主动限制执行引擎使用处理器的寄存器和高速缓存来提升指令执行速度,也没主动限制编译器对于指令的重排序,也就是说在JMM这个模型之上,仍然会存在缓存一致性问题和指令重排序问题。JMM是一个抽象模型,它是建立在不同的操作系统和硬件层面之上对问题进行了统一的抽象,然后再Java层面提供了一些高级指令,让用户选择在合适的时候去引入这些高级指令来解决可见性问题。
导致可见性问题有两个因素,一个是高速缓存导致的可见性问题,另一个是指令重排序。那JMM是如何解决可见性和有序性问题的呢?
分析硬件层面的内容时,已经提到过了,对于缓存一致性问题,有总线锁和缓存锁,缓存锁是基于MESI协议。而对于指令重排序,硬件层面提供了内存屏障指令。而JMM在这个基础上提供了volatile、final等关键字,使得开发者可以在合适的时候增加相应相应的关键字来禁止高速缓存和禁止指令重排序来解决可见性和有序性问题。
除了显示引用volatile关键字能够保证可见性以外,在Java中,还有很多的可见性保障的规则。
从JDK1.5开始,引入了一个happens-before的概念来阐述多个线程操作共享变量的可见性问题。所以我们可以认为在JMM中,如果一个操作执行的结果需要对另一个操作可见,那么这两个操作必须要存在happens-before关系。这两个操作可以是同一个线程,也可以是不同的线程。
int a = 0;
int b = 0;
void test(){
int a = 1; a
int b = 1; b
//int b = 1;
//int a = 1;
int c=a*b; c
}
a happens -before b ; b happens before c
a happens-before b , b happens- before c, a happens-before c
volatile 修饰的变量的写操作,一定happens-before后续对于volatile变量的读操作.
内存屏障机制来防止指令重排.
int x = 10;
synchronized(this){
//后序线程读取到的x的值一定是12
if(x<12){
x= 12;
}
}
x=12;
public class StartDemo(){
int x = 0;
Thread t1 = new Thread(()->{
//读取x的值,一定是20
if(x==20){
}
});
}
x = 20;
t1.start();
public class Test{
int x = 0;
Thread t1 = new Thread(()->{
x = 200;
})
t1.start();
t1.join();//保证结果的可见性。
System.out.println(x);//在此处读到的x的值一定是200。
}
标签:tor 通过 内存地址 如何 硬件 问题 thread 生成 memory
原文地址:https://www.cnblogs.com/nangonghui/p/13122363.html