Java拾遗2 - volatile和内存屏障和cpu指令

某一天看书, 发现AtomicInteger里面有个lazySet方法. 很奇怪 不知道是干啥. 所以就有了这篇文章.
更多拾遗系列文章

lazySet方法

源代码:

    /**
     * Eventually sets to the given value.
     *
     * @param newValue the new value
     * @since 1.6
     */
    public final void lazySet(int newValue) {
        unsafe.putOrderedInt(this, valueOffset, newValue);
    }
    // 一般的set方法
        /**
     * Sets to the given value.
     *
     * @param newValue the new value
     */
    public final void set(int newValue) {
        value = newValue;
    }

其中value是volatile类型. 于是看了这篇文章: JUC中Atomic class之lazySet的一点疑惑 里面解释了lazySet和set的区别 就是减少了一个StoreLoad屏障.

为啥会有内存屏障

我们知道cpu和内存之间实际上会有各级缓存在这里插入图片描述
在多线程环境下, 每个CPU都有自己缓存的数据, 那么对于同一数据 可能在多个线程 (有可能在多个核心上执行)就很可能出现数据不一样的情况? 那么我们如何保证这样的程序是正确的或者可预期的呢? 而不是毫无头绪. 所以我们需要一种机制来让别的处理器知道数据可能会过期. 所以就有了下面的几种内存屏障原文:

  • LoadLoad屏障:对于这样的语句Load1; LoadLoad; Load2,在Load2及后续读取操作要读取的数据被访问前,保证Load1要读取的数据被读取完毕。
  • StoreStore屏障:对于这样的语句Store1; StoreStore; Store2,在Store2及后续写入操作执行前,保证Store1的写入操作对其它处理器可见。
  • LoadStore屏障:对于这样的语句Load1; LoadStore; Store2,在Store2及后续写入操作被刷出前,保证Load1要读取的数据被读取完毕。
  • StoreLoad屏障:对于这样的语句Store1; StoreLoad; Load2,在Load2及后续所有读取操作执行前,保证Store1的写入对所有处理器可见。它的开销是四种屏障中最大的。在大多数处理器的实现中,这个屏障是个万能屏障,兼具其它三种内存屏障的功能。
    ————————————————

有这么多种是不是很难记? 实际上就3种, LoadLoad, LoadStore, StoreLoad. 因为StoreStore并不能保证Load操作不被重排序, 所以在cpu中都没有实现. 而在intel cpu中, LoadLoad对应了lfence指令. 这里描述了内存屏障,

在这里插入图片描述
保证LoadLoad之前的所有Load操作都已经完成.

LoadStore对应了sfence指令.
在这里插入图片描述
保证的是 屏障前面的每个Store对屏障后的每个Store都可见.

StoreLoad对应了mfence.
在这里插入图片描述

为啥StoreLoad是最慢的?

这里这里 解释了为啥StoreLoad屏障是最慢的.
从上面的StoreLoad/mfence可以看到, StoreLoad保证 屏障前面的所有Load. Store对屏障后的所有Load,或者Store都可见. – 很明显这个就很强了.

为啥会有重排序?

这里 很好的解释了为啥要重排序, 以及为啥重排序会更快.

References

  1. volatile使用的lock指令 - http://ifeve.com/juc-atomic-class-lazyset-que/
  2. 所有的指令下载地址百度盘
    链接: https://pan.baidu.com/s/1Wah0C3718BFo5idc0dQaWw 提取码: nmux 复制这段内容后打开百度网盘手机App,操作更方便哦
    在这里插入图片描述

猜你喜欢

转载自blog.csdn.net/scugxl/article/details/103292014