Java中的伪共享(false sharing)
1.引入
最近在看JVM最新的一版,挺不错的比之前的第二版增加介绍了jdk8之后JVM的改变等。(推荐大家去看!!!)然后在卡表哪里提到了伪共享,于是就有了下面的文章了。(可能有很多大佬都写过了,我在来写一遍,加强我自己的理解吧。)
2.什么是伪共享?
维基百科对于伪共享的解释:CPU的缓存是以缓存行(cache line)为单位进行缓存的,当多个线程修改不同变量,而这些变量又处于同一个缓存行时就会影响彼此的性能。例如:线程1和线程2共享一个缓存行,线程1只读取缓存行中的变量1,线程2修改缓存行中的变量2,虽然线程1和线程2操作的是不同的变量,由于变量1和变量2同处于一个缓存行中,当变量2被修改后,缓存行失效,线程1要重新从主存中读取,因此导致缓存失效,从而产生性能问题。为了更深入一步理解伪共享,我们先看一下CPU缓存。
总体来说就是线程同时访问缓存行导致了,数据在缓存行里的缓存数据失效。
3.java中的伪共享问题
private static final int DIMENSION_1 = 1024 * 1024;
private static final int DIMENSION_2 = 62;
private static long[][] longs;
public static void main(String[] args) throws Exception {
longs = new long[DIMENSION_1][];
for (int i = 0; i < DIMENSION_1; i++) {
longs[i] = new long[DIMENSION_2];
for (int j = 0; j < DIMENSION_2; j++) {
longs[i][j] = 0L;
}
}
System.out.println("starting....");
final long start = System.currentTimeMillis();
long sum = 0L;
for (int r = 0; r < 10; r++) {
// 1----------------------------
for (int j = 0; j < DIMENSION_2; j++) {
for (int i = 0; i < DIMENSION_1; i++) {
sum += longs[i][j];
}
}
// 2----------------------------
// 3----------------------------
// for (int i = 0; i < DIMENSION_1; i++) {
// for (int j = 0; j < DIMENSION_2; j++) {
// sum += longs[i][j];
// }
// }
// 4----------------------------
}
System.out.println("执行时间 = " + (System.currentTimeMillis() - start));
}
3-4行快得到原因:
3-4行运算,每次开始内循环时,从内存抓取的数据块实际上覆盖了longs[i][0]到longs[i][5]的全部数据(刚好64字节)。
因此,内循环时所有的数据都在L1缓存可以命中,遍历将非常快。
1-2行慢的原因:
1-2行运算,每次从内存抓取的都是同行不同列的数据块(如longs[i][0]到longs[i][5]的全部数据),但循环下一个的目标,
却是同列不同行(如longs[0][0]下一个是longs[1][0],造成了longs[0][1]-longs[0][5]无法重复利用
4.java中的伪共享问题
Java 8 中已经提供了官方的解决方案,Java 8 中新增了一个注解: @sun.misc.Contended。加上这个注解的类会自动补齐缓存行,需要注意的是此注解默认是无效的,需要在 jvm 启动时设置 -XX:-RestrictContended 才会生效。
同时ConcurrentHashMap 中也用到了这个@sun.misc.Contended 注解来解决伪共享。
/**
* A padded cell for distributing counts. Adapted from LongAdder
* and Striped64. See their internal docs for explanation.
*/
@sun.misc.Contended static final class CounterCell {
volatile long value;
CounterCell(long x) { value = x; }
}
-XX:+UseCondCardMark,用来决定是否开启 卡表更新的条件判断。这个是解决内存回收时的卡表判断数据是否是需要回收的数据,来解决伪共享问题。
但是实际开发中一般很难遇到。如果真的遇到了,那将是一个奇怪知识爆增的时机。
引入文章:https://blog.csdn.net/xiamiflying/article/details/80910680