多线程的可见性
一、深入探究多线程的不确定性因素
1 1、cpu高速缓存导致的极短时间内获取不到想要数据 2 2、cpu指令重排导致之后的自行优化导致数据混乱;当然,这种情况值存在于多线程的基础上,单个线程是不会出现这种问题的。 3 3、JIT会把多次方法区中多调用或者多次循环的数据进行换存,然后自行优化;这里缓存也会导致后来改变的数据无法被正常的获取。
首先来说cpu的高速缓存,它是介于我们物理内存与程序之间。
CPU高速缓存(英语:CPU Cache,在本文中简称缓存)是用于减少处理器访问内存所需平均时间的部件。在金字塔式存储体系中它位于自顶向下的第二层,仅次于CPU寄存器。
其容量远小于内存,但速度却可以接近处理器的频率。
当处理器发出内存访问请求时,会先查看缓存内是否有请求数据。如果存在(命中),则不经访问内存直接返回该数据;如果不存在(失效),则要先把内存中的相应数据载入缓存,再将其返回处理器。
缓存之所以有效,主要是因为程序运行时对内存的访问呈现局部性(Locality)特征。这种局部性既包括空间局部性(Spatial Locality),也包括时间局部性(Temporal Locality)。
有效利用这种局部性,缓存可以达到极高的命中率。
在处理器看来,缓存是一个透明部件。因此,程序员通常无法直接干预对缓存的操作。但是,确实可以根据缓存的特点对程序代码实施特定优化,从而更好地利用缓存。
这里说吧内存中的相应数据载入缓存,再将其返回处理器。在多线程的场景下,就会导致在极短的时间内导致数据获取不到。
CPU指令重排:这种情况在单个线程下怎么重排都无所谓,不会出现值混乱,但是在多线程的环境下就可能出现值混乱。
1 //就是说Java 编译器可能重新排列源代码执行的顺序来优化编译的表现,比如 2 3 int a = 0, 4 int b = a + 1; 5 int c = 2; 6 7 //在编译成bytecode 以后,执行的顺序可能是 8 int c = 2; 9 int b = a + 1; 10 int a = 0;
JIT执行模型:
二、JMM内存模型
Java内存模型中规定:
> 对某个 volatile 字段的写操作 happens-before 每个后续对该 volatile 字段的读操作。
> 对 volatile 变量 v 的写入,与所有其他线程后续对 v 的读同步;
Volatile如何实现它的语义:
> 禁止缓存;
volatile变量的访问控制符会加个ACC_VOLATILE
https://docs.oracle.com/javase/specs/jvms/se8/html/jvms-4.html#jvms-4.5
> 对volatile变量相关的指令不做重排序;
以此来保证多线程的可见性。