先了解下重排序
在很多情况下,访问一个程序变量(对象实例字段,类静态字段和数组元素)可能会使用不同的顺序执行,而不是程序语义所指定的顺序执行。编译器能自由的以优化的名义去改变指令顺序。在特定的环境下,处理器可能会次序颠倒的执行指令。数据可能在寄存器,处理器缓冲区和主内存中以不同的次序移动,而不是按照程序制定的顺序。
例如,如果一个线程写入值到字段a,然后写入值到字段b,而且b的值不依赖于a的值,那么处理器就能自由的调整它们的执行顺序,而且缓冲区能够在a之前刷新b的值到主内存。有很多潜在的重排序的来源,比如:编译器,JIT和缓冲区。
编译器,运行时和硬件被期望一起协力创建好像是顺序执行语义的假象,这意味着在单线程的程序中,程序应该是不能够观察到重排序的影响。但是,重排序在没有正确同步了的多线程程序中开始起作用,在这些多线程中,一个线程能够观察到其他线程的影响,也可能检测到其他线程将会以一种不同于程序语义所规定的执行顺序来访问变量。
大部分情况下,一个线程不会关注其他线程正在做什么,但是当它需要关注的时候,那就需要同步了。
什么是内存模式
- 在处理器层面上,内存模型定义了充要条件:
让当前处理器可以看到其它处理器写入到内存的数据,以及其他处理器可以看到当前处理器写入到内存的数据。
"一个线程的写操作对其他线程可见" 是因为编译器对代码进行重排序导致的。比如,只要代码移动不改变程序的语义,并且编译器认为程序的一个写操作到后面会更好的时候,编译器就会对代码进行移动。如果编译器推迟一个操作A,其他线程可能在这个操作A执行完成前都看不到该操作A的结果,这反映了缓存的影响。
当写入内存的操作能被移动到程序里更前的时候,其他的线程在程序中可能看到一个比它实际发生更早的写操作。这样灵活性的设计是为了通过给编译器,运行时或硬件灵活性使其能在最佳顺序的情况下执行操作。在内存模型的限定之内,我们能够获取更高的性能。
举例:
ClassReordering{
int x = 0, y = 0;
public void writer(){
x = 1;
y = 2;
}
public void reader(){
int r1 = y;
int r2 = x;
}
}
让我们在两个并发线程中执行这段代码,读取Y变量,值是2,因为这个写入比写到X变量更晚一些,你可能认为读取X变量将肯定会得到1。但是,写入操作可能被重排序过。如果重排序发生了,那么写入Y变量后,跟着的就是两个读操作,然后X的写入可能发生了。所以结果可能是r1的值是2,r2的值是0。
Java内存模型描述了:
- 在多线程里什么行为是合法的,以及线程如何通过内存进行交互。
- “程序中的变量”和“从内存或者寄存器获取存储它们的底层细节”之间的关系
java内存模型通过使用各种各样的硬件和编译器的优化来正确实现以上事情
java 包含了几个语言级别的关键字,包括:volatile,final和synchronized,目的是为了帮助程序员向编译器描述一个程序的并发需求。Java内存模型定义了volatile和synchronized的行为,更重要的是保证了同步的java程序在所有的处理器架构下面都能正确的运行。