聊聊Java内存模型
一、Java内存模型硬件处理电脑硬件,我们知道有用于计算的cpu、辅助运算的内存、以及硬盘还有进行数据传输的数据总线。在程序执行中很多都是内存计算,cpu为了更快的进行计算会有高速缓存,最后同步至主内存,大概的交互如下图 ? 为了使处理器内部的运算单元能够被充分的利用,处理器可能会对输入代码进行乱序执行优化,然后将计算后的结果进行重组,保证该结果和顺序执行的结果是一致的(单位时间内,一个core只能执行一个线程,所以结果的一致仅限一个线程内)。 Java内存模型Java内存模型是语言级别的模型,它的主要目标是定义程序中各个变量的访问规则,即在虚拟机中将变量存储到内存和从内存中取数变量这样的底层细节。 在内存里,java内存模型规定了所有的变量都存储在主内存(物理内存)中,每条线程还有自己的工作内存,线程对变量的所有操作都必须在工作内存中进行。不同的线程无法访问别线程的工作内存里的内容。下图展示了逻辑上 线程、主内存、工作内存的三者交互关系。 ? 内存交互操作Java内存模型定义的8个操作指令来进行内存之间的交互
下图展示下工作内存和主内存间的指令操作交互 ? 指令规则
二、重排序从java源码到最终实际执行的指令序列,会经历下面3种重排序 ? 重排序的现象
重排序的代码示例,文章底部的参考文章里有示例,这里就不罗列了。 三、内存屏障JMM(java 内存模型) 在不改变程序执行结果的前提下,尽可能的支持处理器的重排序。通过禁止特定特定类型的编译器重排序和处理器重排序,为开发者提供一致的内存可见性保证,如 Java编译器在生成指令的时候会在适当位置插入内存屏障来进制特定类型的处理器排序。 内存屏障说的通俗一点就是一个栏杆,在两个指令之间插入栏杆,后面的指令就不能越过栏杆先执行。 JMM定义的内存屏障指令分为4类
处理器对重排序的支持 ? 从上面可以看到不同的处理器架构对重排序的支持也是不一样(其它处理器架构暂不罗列),所以不同的平台JMM的内存屏障施加也略有不同,具体来说,比如 X86 对Load1Load2不支持重排序,那么你就没有必要施加 四、volatile的内存语义volatile我们都知道是java的关键字用来保证数据可见性,防止指令重排的效果。包括JUC里AQS Lock的底层实现也是基于volatitle来实现。 volatile写的内存语义
volatile读的内存语义
上面两个语义,保证了volatile变量写入对线程的可见性 volatile内存屏障插入规则 代码简单示例
i = a;
j = b;
i = v;
j = u;
a = i;
b = j;
v = i;
u = j;
i = u;
j = b;
a = i;
}
上述代码可以套用volatile屏障规则对应。 当然不同的处理器架构重排序的支持也是不一样,比如X86 只有当 store1 load2 的时候会进行重排序,那么就会省略掉很多类型的内存屏障。 五、final的内存语义final在Java中是一个保留的关键字,可以声明成员变量、方法、类以及本地变量。 我们暂时把final修饰的称作域,对于final域,编译器和处理器要遵守两个重排序规则 写规则
JMM禁止编译器把final域的写重排序到构造函数之外 该规则可以保证在对象引用为任意线程可见之前,对象的final域已经被正确初始化,而普通域无法保障。 读规则
在一个线程中,初次读对象引用与初次读该对象包含的final域,JMM禁止处理器重排序这两个操作。编译器会在读final域操作的前面插入一个LoadLoad屏障。 该规则保证在读一个对象的final域之前,一定会先读包含这个域的对象引用。 参考资料 (编辑:李大同) 【声明】本站内容均来自网络,其相关言论仅代表作者个人观点,不代表本站立场。若无意侵犯到您的权利,请及时与联系站长删除相关内容! |