Java多线程之有序性(二)
好了,上一章我们讲了有序性出现问题的原因,今天我们来细讲一下有序性的保证方式。
书上有一段比较匪夷所思的关于有序性定义。
有序性:指在什么情况下一个处理器上运行的一个线程所执行的内存访问在另一处理器运行的另外一个线程看来是乱序的,
乱序是指内存访问的顺序看起来像是发生了变化。
我不随意揣测这个定义了,看不懂也没关系,我们接着看下面就好了。
我对有序性的理解是,对于共享变量的操作一定要按我们的代码逻辑顺序执行,避免多线程情况下由于重排导致的错误。细点说就是,临界区内与临界区外的代码你可以重排序,我管不着,因为你不影响共享变量的值。但是,你不能将临界区内的代码重排到临界区外面,当然临界区外的代码进入临界区其实是没影响的。
那么java底层是靠什么实现的呢?靠的是一种叫做内存屏障的东西。
内存屏障:针对跨处理器的读写操作,它被插入到两个指令之间,作用是禁止编译器和处理器重排序。为了禁止重排序,就像是在指令之间建起了一堵墙。
同时,内存屏障也有一个作用就是保证了可见性,因为它可以产生刷新处理与冲刷处理器的效果。先看一张图,

我们按可见性分类,把屏障可以分为两类:加载屏障(Load Barrier)和存储屏障(Store Barrier)。
加载屏障:刷新处理器缓存。
存储屏障:冲刷处理器缓存。
JVM会在MonitorEnter(申请锁)对应的机器码指令后面临界区代码开始之前插入Load Barrier,以达到临界区内部使用的共享变量都是新值。同样,会在MonitorExit(释放锁)对应的指令后插入Store Barrier,以达到临界区对共享变量的更改及时写回主存。
再按有序性划分:分为获取屏障(Acquire Barrier)和释放屏障(Release Barrier)。
获取屏障:在一个读操作之后插入一个屏障,禁止与之后的任何读写操作重排。
释放屏障:在一个写操作之前插入一个屏障,禁止与其前面任何读写操作重排。
JVM会在MonitorEnter(申请锁)对应的机器码指令后面临界区代码开始之前插入Acquire Barrier,并在临界区之后MonitorExit之前插入Release Barrier。
到这,三个重要的性质就讲完了,之后再陆陆续续写点别的,也只是把我的笔记誊抄一遍,还有很多东西我理解的也不深,很表面,甚至会有错误,希望大佬不吝赐教。