@lzb1096101803
2016-03-12T12:17:25.000000Z
字数 1492
阅读 347
电话面试
lock unlock
read-load-use-assign-store-write
只规定按顺序进行,没保证要连续进行
屏蔽掉各种硬件和操作系统的内存访问差异,以实现让Java程序在各种平台下能够达到一致的内存访问效果。C++直接使用物理硬件和操作系统的内存模型
它在多线程的情况下尤其重要。Java内存模型对一个线程所做的变动能被其它线程可见提供了保证,它们之间是先行发生关系。这个关系定义了一些规则让程序员在并发编程时思路更清晰。比如,先行发生关系确保了:
线程内的代码能够按先后顺序执行,这被称为程序次序规则。
对于同一个锁,一个解锁操作一定要发生在时间上后发生的另一个锁定操作之前,也叫做管程锁定规则。
前一个对volatile的写操作在后一个volatile的读操作之前,也叫volatile变量规则。
一个线程内的任何操作必需在这个线程的start()调用之后,也叫作线程启动规则。
一个线程的所有操作都会在线程终止之前,线程终止规则。
一个对象的终结操作必需在这个对象构造完成之后,也叫对象终结规则。
可传递性.
volatile是一个特殊的修饰符,只有成员变量才能使用它。
1. 保证此变量对所有线程的可见性,一个线程修改了这个变量的值,新值对于其它线程来说可以立即得知,保证新值能立即同步到主内存
2. 禁止指令重排序
volatile变量可以保证下一个读取操作会在前一个写操作之后发生,就是上一题的volatile变量规则。
指令重排序
编译器和处理器不会对存在数据依赖关系的操作做重排序,因为这种重排序会改变执行结果。但是,如果操作之间不存在数据依赖关系,这些操作可能被编译器和处理器重排序。
从上图来看,线程A与线程B之间如要通信的话,必须要经历下面2个步骤:
首先,线程A把本地内存A中更新过的共享变量刷新到主内存中去。
然后,线程B到主内存中去读取线程A之前已更新过的共享变量。
JMM通过控制主内存与每个线程的本地内存之间的交互,来为java程序员提供内存可见性保证。
在执行程序时为了提高性能,编译器和处理器常常会对指令做重排序
从java源代码到最终实际执行的指令序列,会分别经历下面三种重排序:
上述的1属于编译器重排序,2和3属于处理器重排序。这些重排序都可能会导致多线程程序出现内存可见性问题。
现代的处理器使用写缓冲区来临时保存向内存写入的数据。
每个处理器上的写缓冲区,仅仅对它所在的处理器可见。这个特性会对内存操作的执行顺序产生重要的影响:处理器对内存的读/写操作的执行顺序,不一定与内存实际发生的读/写操作顺序一致!为了具体说明,请看下面示例:
Processor A Processor B
a = 1; //A1 b = 2; //B1
x = b; //A2 y = a; //B2
初始状态:a = b = 0
处理器允许执行后得到结果:x = y = 0
从内存操作实际发生的顺序来看,直到处理器A执行A3来刷新自己的写缓存区,写操作A1才算真正执行了。虽然处理器A执行内存操作的顺序为:A1->A2,但内存操作实际发生的顺序却是:A2->A1。此时,处理器A的内存操作顺序被重排序了(处理器B的情况和处理器A一样,这里就不赘述了)。
这里的关键是,由于写缓冲区仅对自己的处理器可见,它会导致处理器执行内存操作的顺序可能会与内存实际的操作执行顺序不一致。由于现代的处理器都会使用写缓冲区,因此现代的处理器都会允许对写-读操做重排序。