java伪共享(false sharing)和缓存行(cache line)以及几种锁的性能比较

一、缓存行(cache line)

CPU的缓存是以缓存行(cache line)为单位进行缓存的,当多个线程修改不同变量,而这些变量又处于同一个缓存行时就会影响彼此的性能。

缓存行通常是 64 字节,并且它有效地引用主内存中的一块地址。一个 Java 的 long 类型是 8 字节,因此在一个缓存行中可以存 8 个 long 类型的变量。

二、缓存行的填充

解决伪共享最直接的方法就是填充(padding),比如一个long占8个字节,Java的对象头在32位系统下占用32位(4个字节),在64位系统下占用64位(8个字节)。一个缓存行64字节,那么我们可以填充6个long(一个long和6个用于填充得long,总共:8+6 * 8 = 56个字节),在64位虚拟机下正好占用64字节,在32位虚拟机下则没有超过一个缓存行,这样就能避免多个VolatileLong共享缓存行。

由此可知,java使用1个long字段和6个用于填充得long实现填满整个缓存行,从而避免伪共享(false sharing)的目的。

三、@sun.misc.Contended注解


1、jdk8开始使用@sun.misc.Contended,对某字段加上该注解则表示该字段会单独占用一个缓存行(Cache Line),避免伪共享,启动虚拟机时带上-XX:-RestrictContended  参数


2、jdk7中虚拟机会对无用的字段进行优化导致无法利用7个long来填充缓存行的目的,尽量避免使用jdk7

四、虚拟机对象字段顺序自动排列


对于HotSpot JVM,所有对象都有两个字长的对象头。第一个字是由24位哈希码和8位标志位(如锁的状态或作为锁对象)组成的Mark Word。第二个字是对象所属类的引用。如果是数组对象还需要一个额外的字来存储数组的长度。每个对象的起始地址都对齐于8字节以提高性能。因此当封装对象的时候为了高效率,对象字段声明的顺序会被重排序成下列基于字节大小的顺序:
    doubles (8) 和 longs (8)
    ints (4) 和 floats (4)
    shorts (2) 和 chars (2)
    booleans (1) 和 bytes (1)
    references (4/8)
    <子类字段重复上述顺序>

五、几种线程共享性能比较


单线程无锁:1(以单线程无锁情况下的性能作为基点,越大越慢)
单线程lock锁:33
单线程cas锁:19
单线程volatile:16
多线程lock锁:747
多线程cas锁:100

猜你喜欢

转载自blog.csdn.net/eguid/article/details/108638469