在讲解相关的定义之前,先了解一下CPU在读取数据时候的工作顺序。
CPU要读取一个数据时,首先从Cache中查找,如果找到就立即读取并送给CPU处理;如果没有找到,就用相对慢的速度从内存中读取并送给CPU处理,同时把这个数据所在的数据块调入Cache中,可以使得以后对整块数据的读取都从Cache中进行,不必再调用内存。
其中Intel从Pentium开始将Cache分开,通常分为一级高速缓存L1和二级高速缓存L2。
读写模型
1.原子性
关于原子性解释:指不会被线程调度机制打断的操作;这种操作一旦开始,就一直运行到结束,中间不会有任何 context switch (切换到另一个线程)。一个经典的例子
i++
这个操作会被分解为
1. 读取i
2. 进行+1操作
3. 写入到缓存
当一个线程执行的时候上面的操作肯定是原子性的。但是当两个线程进行操作的时候,可能发生下面的情况
第一个线程将寄存器中的值读为0。
第一个线程为值加1,计数器的值应为1,但在将新值写回寄存器之前,它可能会暂停,同时第二个进程正在运行:
第二个线程读取寄存器中的值,该值仍然等于0;
第二个线程为值添加一个;
第二个线程将新值写入寄存器,寄存器现在的值为1。
而实际情况运行结果应该是2。这个操作是体现了原子性的重要性。
2.可见性
我对可见性的解释:当一个共享变量被多个线程进行操作的时候,如果变量的值被一个线程修改了,那么另外的一个线程能够知道这个变量的新值。
导致可见性的原因也是CPU的缓存结构导致的,数据在第一次读取的时候会从主存中拿出来,然后保存在缓冲中,而在读取值的时候每个线程会去先去缓存中获取值。在对变量进行写操作的时候,才会将新的值刷新到主存中。上面再原子性的举例中就能看到这个问题。
3.顺序一致性
顺序一致性:顺序一致性内存模型是一个理论模型。有两大特征
1.一个线程中的所有操作必须按照程序的顺序来执行
2.所有线程都只能看到一个单一的操作执行顺序。(每个操作都必须原子执行且必须立刻对所有的线程可见)
在java内存模型中,只保证在不改变程序执行结果的前提下,尽可能地为编译器和处理器的优化提供方便。
4.happen-before
happen-before关系:
1.代码的执行顺序,编写在前面的发生在编写在后面的
2.unlock必须发生在lock之后
3.volatile修饰的变量,对一个变量的写操作,先于对该变量的读操作
4.传递规则:操作A先于B,B先于C,那么A肯定先于C
5.线程启动规则:start方法肯定先于线程run
6.线程终端规则:线程的中断方法interrupt动作,必须发生在捕获该动作之前
7.对象初始化必须发生在finalize之前。
8.所有的操作都发生在线程死亡之前。
网友评论