java并发-4-volatile内存实现和内存屏障

分类: java |
一、
为了保证内存可见性,java编译器在生成指令序列的适当位置会插入内存屏障来禁止特定类型的处理器重排序。Java memory model把内存屏障指令分为4类:
http://s3/mw690/002i2qf8zy7jEXEokjEf2&690
这里所有的store都是刷新到内存而不是到缓存!所有的Load都是从主存中加载!
二、
1.
1)
写入当前缓存立刻刷新到主存(总线lock指令),并告知持有该内存的其他缓存备份失效,从而强制重新读取主存。
2)
仅对单个volatile变量的读、写具有原子性
3)
参加内存实现和增强有序性。
2.
从JSR-133开始(即从JDK5开始),volatile变量的写-读可以实现线程之间的通信。
从内存语义的角度来说,volatile的写-读与锁的释放-获取有相同的内存效果:volatile写和锁的释放有相同的内存语义;volatile读与锁的获取有相同的内存语义。
1)
2)
线程A写一个volatile变量,实质上是线程A向接下来将要读这个volatile变量的某个线程 发出了(其对共享变量所做修改的)消息。
线程B读一个volatile变量,实质上是线程B接收了之前某个线程发出的(在写这个volatile 变量之前对共享变量所做修改的)消息。
线程A写一个volatile变量,随后线程B读这个volatile变量,这个过程实质上是线程A通过 主内存向线程B发送消息。
3.
前文提到过重排序分为编译器重排序和处理器重排序。为了实现volatile内存语义,JMM 会分别限制这两种类型的重排序类型。表3-5是JMM针对编译器制定的volatile重排序规则表:
http://s2/mw690/002i2qf8zy7jEXFJTjPf1&690
从表3-5我们可以看出。当第二个操作是volatile写时,不管第一个操作是什么,都不能重排序。这个规则确保 volatile写之前的操作不会被编译器重排序到volatile写之后。当第一个操作是volatile读时,不管第二个操作是什么,都不能重排序。这个规则确保 volatile读之后的操作不会被编译器重排序到volatile读之前。当第一个操作是volatile写,第二个操作是volatile读时,不能重排序。
为了实现volatile的内存语义,编译器在生成字节码时,会在指令序列中插入内存屏障来 禁止特定类型的处理器重排序。对于编译器来说,发现一个最优布置来最小化插入屏障的总 数几乎不可能。为此,JMM采取保守策略。下面是基于保守策略的JMM内存屏障插入策略。
1)
2)
3)
4)
总之,volatile读操作内存屏障指令序列如下图2:
http://s15/mw690/002i2qf8zy7jEXGkx3M8e&690
总之,volatile写操作内存屏障指令序列如下图3:
http://s3/mw690/002i2qf8zy7jEXHjB2W82&690
所以,普通读/写仍有可能与volatile读/写发生重排序。
例如图2位于volatile读之前的普通读/写都可以排到volatile读之后;图3位于volatile写之前的普通读可以位于volatile写之后(但被StoreLoad挡住),位于volatile写之后的普通写可以位于volatile写之前(但被StoreStore挡住)。
这种受限的重排序与表3-5中的设定不一致?
至于如何实现volatile可见性,假设有volatile写之后紧接着volatile读,那么由于内存屏障存在,读操作不可能位于写之前,又由于StoreLoad指令,保证写完后直接保存到内存而不是缓存,那么下面volatile读的时候就可以从主存重新读取到最新值(如何通过内存屏障指令通知其他共有缓存失效?)。
所以可以通过内存屏障限制指令重排序,从一定程度上保证可见性。
4.
旧的java内存模型中,虽然不允许volatile变量之间重排序,但旧的Java内 存模型允许volatile变量与普通变量重排序。
http://s6/mw690/002i2qf8zy7jEXIgpdr05&690
如上例子,分别有两个线程进行writer和reader,由于a和flag没有直接依赖关系,可能会出现如下过程,导致线程B读到了未赋值的共享变量a。
http://s6/mw690/002i2qf8zy7jEXJh0Il25&690
根据2中volatile内存实现的增强后的volatile限制了volatile变量和普通变量的重排序,确保volatile的写-读和锁的释放-获取有相同的内存语义,一旦编译器发现重排序会破坏volatile的内存语义,就会禁止这种重排序。按照内存屏障指令的设定,此时上例中的执行顺序必然为1,2,3,4,没有发生任何重排序。
当然,编译器进行指令优化reordering时,并不是说volatile执行顺序与其他非volatile执行顺序不变,当然为了方便理解,可以近似认为volatile变量和普通变量不能重排序。