伪共享的样子:
Java view: public class VolatileLong { volatile long v = 0L; } Memory view: ...–––-)(––––HV––––HV–––)(–––...
我们看到,两个VolatileLong对象被load到了同一个缓存行里面,如果一个线程要修改对象1,另一个线程同时要修改对象2,此时就要面对伪共享这个无形的性能杀手了
jdk6中的解决办法:
Java view: public class VolatileLong { volatile long p0, p1, p2, p3, p4, p5, p6; volatile long v = 0L; volatile long q0, q1, q2, q3, q4, q5, q6; } Memory view: ...–––-)(ppppppHVqqqqqq)(–––...
很多大神的代码都是通过上面的方式也就是long padding来避免伪共享
例如:
1.Doug Lea的jsr166中早期的LinkedTransferQueue版本 http://gee.cs.oswego.edu/cgi-bin/viewcvs.cgi/jsr166/src/jsr166y/LinkedTransferQueue.java?revision=1.1&view=markup
2.还是Doug Lea的ConcurrentHashMapV8 http://gee.cs.oswego.edu/cgi-bin/viewcvs.cgi/jsr166/src/jsr166e/ConcurrentHashMapV8.java?revision=1.121&view=markup(在java8中的版本已经用sun.misc.Contended替换long padding)
3.大名鼎鼎的无锁并发框架Disruptor https://github.com/LMAX-Exchange/disruptor
4.等等等...
long padding的解决办法不怎么优雅,并且在jdk7某个版本以后能会优化掉long padding,尼玛java程序员是有多难啊,想尽了一切办法对付jdk7中的这个优化,详情点击 http://ifeve.com/false-sharing-java-7/
但是但是...开始使用jdk8的同学注意了,java8已经给出了官方的解决办法...
就是sun.misc.Contended注解 http://mail.openjdk.java.net/pipermail/hotspot-dev/2012-November/007309.html
Java view: // jdk8新特性,Contended注解避免false sharing // Restricted on user classpath // Unlock: -XX:-RestrictContended @sun.misc.Contended public class VolatileLong { volatile long v = 0L; } Memory view: ...–––-)(******HV******)(–––...
要注意的是user classpath使用此注解默认是无效的,需要在jvm启动时设置-XX:-RestrictContended
jdk8中已经使用sun.misc.Contended的地方:
src/share/classes/java/util/concurrent/ConcurrentHashMap.java 2458: @sun.misc.Contended static final class CounterCell { src/share/classes/java/util/concurrent/Exchanger.java 310: * bookkeeping. Padded via @sun.misc.Contended to reduce m 313: @sun.misc.Contended static final class Node { src/share/classes/java/util/concurrent/ForkJoinPool.java 161:@sun.misc.Contended 643: @sun.misc.Contended src/share/classes/java/util/concurrent/atomic/Striped64.java 55: * (via @sun.misc.Contended) to reduce cache contention. Pa 119: @sun.misc.Contended static final class Cell { src/share/classes/java/lang/Thread.java 2004: @sun.misc.Contended("tlr") 2008: @sun.misc.Contended("tlr") 2012: @sun.misc.Contended("tlr")
最后,贴上测试代码,感兴趣的各自测试吧
public class FalseSharing implements Runnable { public final static int NUM_THREADS = 4; // change public final static long ITERATIONS = 500L * 1000L * 1000L; private final int arrayIndex; private static VolatileLong3[] longs = new VolatileLong3[NUM_THREADS]; static { for (int i = 0; i < longs.length; i++) { longs[i] = new VolatileLong3(); } } public FalseSharing(final int arrayIndex) { this.arrayIndex = arrayIndex; } public static void main(final String[] args) throws Exception { long start = System.nanoTime(); runTest(); System.out.println("duration = " + (System.nanoTime() - start)); } private static void runTest() throws InterruptedException { Thread[] threads = new Thread[NUM_THREADS]; for (int i = 0; i < threads.length; i++) { threads[i] = new Thread(new FalseSharing(i)); } for (Thread t : threads) { t.start(); } for (Thread t : threads) { t.join(); } } public void run() { long i = ITERATIONS + 1; while (0 != --i) { longs[arrayIndex].value = i; } } public final static class VolatileLong { public volatile long value = 0L; } // long padding避免false sharing // 按理说jdk7以后long padding应该被优化掉了,但是从测试结果看padding仍然起作用 public final static class VolatileLong2 { volatile long p0, p1, p2, p3, p4, p5, p6; public volatile long value = 0L; volatile long q0, q1, q2, q3, q4, q5, q6; } // jdk8新特性,Contended注解避免false sharing // Restricted on user classpath // Unlock: -XX:-RestrictContended @sun.misc.Contended public final static class VolatileLong3 { public volatile long value = 0L; } }
我机器上的测试结果:
VolatileLong: duration = 29594765000 VolatileLong2:duration = 7234555000 VolatileLong3:duration = 7167475000
测试代码来自 http://ifeve.com/falsesharing/,稍有改动