volitle 与 LongAdder

深入理解Java中的volatile关键字-HollisChuang’s Blog

volatile

volatile这个关键字,不仅仅在Java语言中有,在很多语言中都有的,而且其用法和语义也都是不尽相同的。尤其在C语言、C++以及Java中,都有volatile关键字。都可以用来声明变量或者对象。

volatile关键字在C与java语言作用
C语言
(1)阻止编译器为了提高速度将- -个变量缓存到寄存器内而不写回。
(2)阻止编译器调整操作volatile变量的指令顺序。
注意:无法阻止CPU动态调度换序。

以下是Java语言中的volatile关键字功能
::(1)volitle能保证可见性,也就是说只要一个线程对共享变量修改了,其他线程都能立即看到。::
::(2)volitle的第二条语义:禁止指令重排序(注意包含编译器和cpu)::

可见性

Java代码:
volatile Singleton instance = new Singleton();//instance是volatile变量
汇编代码:
0x01a3de1d: movb 0x0,(%esp);

有volatile变量修饰的共享变量进行写操作的时候会多第二行汇编代码,通过查IA-32架构软件开发者手册可知,lock前缀的指令在多核处理器下会引发了两件事情。

  • 将当前处理器缓存行的数据会写回到系统内存。
  • 这个写回内存的操作会引起在其他CPU里缓存了该内存地址的数据无效。

指令重排

阻止编译时(编译器)和运行时(cpu)的指令重排
编译时JVM编译器遵循内存屏障的约束,运行时依靠CPU屏障指令来阻止重排。

CPU屏障指令即内存屏障

内存屏障(Memory Barrier)是一种CPU指令

并发之volatile底层原理 - 左手指月 - 博客园

概念

[image:090F27BD-407F-42DB-80CC-5AEAB99BA3C2-1267-0002F5AC3DB6C4AF/20161111081903485.png]

volitle 与 LongAdder_第1张图片
cacheline

Cache Line

Cache Line可以简单的理解为CPU Cache中的最小缓存单位。目前主流的CPU Cache的Cache Line大小都是64Bytes。假设我们有一个512字节的一级缓存,那么按照64B的缓存单位大小来算,这个一级缓存所能存放的缓存个数就是512/64 = 8个。

false sharing(伪共享)

当多线程修改互相独立的变量时,如果这些变量共享同一个缓存行,就会无意中影响彼此的性能,这就是伪共享。

伪共享(False sharing)和cpu缓存行;网上关于这两个方面的文章也很多,如:
关于CPU Cache — 程序猿需要知道的那些事 ,
7个示例科普CPU CACHE ,
What is @Contended and False Sharing ?

Padding

填充数据结构可以减少”false sharing”,使得线程操作的变量落入到不同的cache line中。
Java 8中引入了一个新注解 @Contented,主要是用来减少“False sharing”,在你需要避免“false sharing”的字段上标记注解,这可以暗示虚拟机“这个字段可以分离到不同的cache line中”。

实现

为了解决cache line带来的false sharing问题,并没有使用AtomicLong,在高并发的单调自增场景,LongAdder提供了比AtomicLong更好的性能。Hystrix和Sentinel都没有直接使用JDK中的LongAdder,copy过来做了简单修改。
不同实现:
sentinel:
com.alibaba.csp.sentinel.slots.statistic.base.LongAdder
com.alibaba.csp.sentinel.slots.statistic.base.Striped64
Hystrix:
com.netflix.hystrix.util.LongAdder
com.netflix.hystrix.util.Striped64
Java8:
java.util.concurrent.atomic.LongAdder
java.util.concurrent.atomic.Striped64

Striped64中使用了一个叫Cell的类,是一个普通的二元算术累积单元,线程也是通过hash取模操作映射到一个Cell上进行累积。为了加快取模运算效率,也把Cell数组的大小设置为2^n,同时大量使用Unsafe提供的底层操作。基本的实现桶1.7的ConcurrentHashMap非常像,而且更简单。

com.alibaba.csp.sentinel.slots.statistic.base.Striped64中Cell实现

static final class Cell {
        volatile long p0, p1, p2, p3, p4, p5, p6;
        volatile long value;
        volatile long q0, q1, q2, q3, q4, q5, q6;
        Cell(long x) { value = x; }
 
        final boolean cas(long cmp, long val) {
            return UNSAFE.compareAndSwapLong(this, valueOffset, cmp, val);
        }
 
        // Unsafe mechanics
        private static final sun.misc.Unsafe UNSAFE;
        private static final long valueOffset;
        static {
            try {
                UNSAFE = getUnsafe();
                Class ak = Cell.class;
                valueOffset = UNSAFE.objectFieldOffset
                    (ak.getDeclaredField("value"));
            } catch (Exception e) {
                throw new Error(e);
            }
        }
 }

说明:这里本质上是让上面的value单独拥有一个缓存行,因为一个缓存行的大小一般是64字节,而value是long类型,占用8个字节,所以在其前后分别加7个Long类型的填充数,那么value一定会单独占用一个缓存行,本质是采用了空间换时间。
但是这种方式毕竟不通用,例如32、64位操作系统的缓存行大小不一样,因此JAVA8中就增加了一个注@sun.misc.Contended解用于解决这个问题,由JVM去插入这些变量。
java.util.concurrent.atomic.Striped64Cell实现

@sun.misc.Contended static final class Cell {
    volatile long value;
    Cell(long x) { value = x; }
    final boolean cas(long cmp, long val) {
        return UNSAFE.compareAndSwapLong(this, valueOffset, cmp, val);
    }

    // Unsafe mechanics
    private static final sun.misc.Unsafe UNSAFE;
    private static final long valueOffset;
    static {
        try {
            UNSAFE = sun.misc.Unsafe.getUnsafe();
            Class ak = Cell.class;
            valueOffset = UNSAFE.objectFieldOffset
                (ak.getDeclaredField("value"));
        } catch (Exception e) {
            throw new Error(e);
        }
    }
}

从AtomicLong到LongAdder,LongAdder的原理 - 进化的深山猿 - CSDN博客

引用

Java多线程——AtomicLong LongAdder源码解析-云栖社区-阿里云
【转载】从LongAdder看更高效的无锁实现 -
图文CPU Cache and Memory Ordering(修改版) - 百度文库

你可能感兴趣的:(volitle 与 LongAdder)