Java8中用sun.misc.Contended避免伪共享(false sharing)

http://budairenqin.iteye.com/blog/2048257

  关于伪共享这个概念,请先参照 http://ifeve.com/falsesharing/ 

伪共享的样子: 
Java代码   收藏代码
  1. Java view:  
  2. public class VolatileLong {  
  3.         volatile long v = 0L;  
  4. }  
  5.   
  6. Memory view:  
  7. ...–––-)(––––HV––––HV–––)(–––...  

我们看到,两个VolatileLong对象被load到了同一个缓存行里面,如果一个线程要修改对象1,另一个线程同时要修改对象2,此时就要面对伪共享这个无形的性能杀手了 

jdk6中的解决办法: 
Java代码   收藏代码
  1. Java view:  
  2. public class VolatileLong {  
  3.         volatile long p0, p1, p2, p3, p4, p5, p6;  
  4.         volatile long v = 0L;  
  5.         volatile long q0, q1, q2, q3, q4, q5, q6;  
  6. }  
  7.   
  8. Memory view:  
  9. ...–––-)(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代码   收藏代码
  1. Java view:  
  2. // jdk8新特性,Contended注解避免false sharing  
  3. // Restricted on user classpath  
  4. // Unlock: -XX:-RestrictContended  
  5. @sun.misc.Contended  
  6. public class VolatileLong {  
  7.         volatile long v = 0L;  
  8. }  
  9.   
  10.   
  11. Memory view:  
  12. ...–––-)(******HV******)(–––...  

要注意的是user classpath使用此注解默认是无效的,需要在jvm启动时设置-XX:-RestrictContended 

jdk8中已经使用sun.misc.Contended的地方: 
Java代码   收藏代码
  1. src/share/classes/java/util/concurrent/ConcurrentHashMap.java  
  2. 2458@sun.misc.Contended static final class CounterCell {  
  3.   
  4. src/share/classes/java/util/concurrent/Exchanger.java  
  5. 310: * bookkeeping. Padded via @sun.misc.Contended to reduce m  
  6. 313@sun.misc.Contended static final class Node {  
  7.   
  8. src/share/classes/java/util/concurrent/ForkJoinPool.java 161:@sun.misc.Contended  
  9. 643@sun.misc.Contended  
  10.   
  11. src/share/classes/java/util/concurrent/atomic/Striped64.java  
  12. 55: * (via @sun.misc.Contended) to reduce cache contention. Pa  
  13. 119@sun.misc.Contended static final class Cell {  
  14.   
  15. src/share/classes/java/lang/Thread.java  
  16. 2004@sun.misc.Contended("tlr")  
  17. 2008@sun.misc.Contended("tlr")  
  18. 2012@sun.misc.Contended("tlr")  



最后,贴上测试代码,感兴趣的各自测试吧 
Java代码   收藏代码
  1. public class FalseSharing implements Runnable {  
  2.   
  3.     public final static int NUM_THREADS = 4// change  
  4.     public final static long ITERATIONS = 500L * 1000L * 1000L;  
  5.     private final int arrayIndex;  
  6.   
  7.     private static VolatileLong3[] longs = new VolatileLong3[NUM_THREADS];  
  8.     static {  
  9.         for (int i = 0; i < longs.length; i++) {  
  10.             longs[i] = new VolatileLong3();  
  11.         }  
  12.     }  
  13.   
  14.     public FalseSharing(final int arrayIndex) {  
  15.         this.arrayIndex = arrayIndex;  
  16.     }  
  17.   
  18.     public static void main(final String[] args) throws Exception {  
  19.         long start = System.nanoTime();  
  20.         runTest();  
  21.         System.out.println("duration = " + (System.nanoTime() - start));  
  22.     }  
  23.   
  24.     private static void runTest() throws InterruptedException {  
  25.         Thread[] threads = new Thread[NUM_THREADS];  
  26.   
  27.         for (int i = 0; i < threads.length; i++) {  
  28.             threads[i] = new Thread(new FalseSharing(i));  
  29.         }  
  30.   
  31.         for (Thread t : threads) {  
  32.             t.start();  
  33.         }  
  34.   
  35.         for (Thread t : threads) {  
  36.             t.join();  
  37.         }  
  38.     }  
  39.   
  40.     public void run() {  
  41.         long i = ITERATIONS + 1;  
  42.         while (0 != --i) {  
  43.             longs[arrayIndex].value = i;  
  44.         }  
  45.     }  
  46.   
  47.     public final static class VolatileLong {  
  48.         public volatile long value = 0L;  
  49.     }  
  50.   
  51.     // long padding避免false sharing  
  52.     // 按理说jdk7以后long padding应该被优化掉了,但是从测试结果看padding仍然起作用  
  53.     public final static class VolatileLong2 {  
  54.         volatile long p0, p1, p2, p3, p4, p5, p6;  
  55.         public volatile long value = 0L;  
  56.         volatile long q0, q1, q2, q3, q4, q5, q6;  
  57.     }  
  58.   
  59.     // jdk8新特性,Contended注解避免false sharing  
  60.     // Restricted on user classpath  
  61.     // Unlock: -XX:-RestrictContended  
  62.     @sun.misc.Contended  
  63.     public final static class VolatileLong3 {  
  64.         public volatile long value = 0L;  
  65.     }  
  66. }  


我机器上的测试结果: 
Java代码   收藏代码
  1. VolatileLong: duration = 29594765000  
  2. VolatileLong2:duration = 7234555000  
  3. VolatileLong3:duration = 7167475000  


测试代码来自 http://ifeve.com/falsesharing/,稍有改动
分享到:   
Java8 中HashMap的优化 |  Java 绕过编译器检查抛出“受检查的”异常
  • 2014-04-18 13:58
  • 浏览 314
  • 评论(4)
  • 分类:编程语言
  • 相关推荐
评论
4 楼  budairenqin 2014-09-15  
liuInsect 写道
      volatile long p0, p1, p2, p3, p4, p5, p6;  
        public volatile long value = 0L;  
        volatile long q0, q1, q2, q3, q4, q5, q6;  
这里 为什么是 13 * 8 个字节 还不是 (7+1)*8?  8*8 =64不是正好是一个cache line么?

是15*8,因为用7个下标保证了会有56个字节填充在数值的任何一边,56字节的填充+8字节的long数值正好装进一行64字节的缓存行。
3 楼  lastashley 2014-06-24  
budairenqin 写道
liuInsect 写道
      volatile long p0, p1, p2, p3, p4, p5, p6;  
        public volatile long value = 0L;  
        volatile long q0, q1, q2, q3, q4, q5, q6;  
这里 为什么是 13 * 8 个字节 还不是 (7+1)*8?  8*8 =64不是正好是一个cache line么?

额...一个cache line确实是64
我水平有限,无法准确回答你这个问题,我说下我的测试结果吧
环境mac os,jdk1.7.15
public final static class VolatileLong2 {
// long p0, p1, p2, p3, p4, p5, p6;
public volatile long value = 0L;
long q0, q1, q2, q3, q4, q5, q6;
}
上面的代码,正好填充了一个cache line,但是会被jdk1.7.15优化掉,也就是说padding失效
而下面代码不会被优化掉,padding有效
public final static class VolatileLong2 {
long p0, p1, p2, p3, p4, p5, p6;
public volatile long value = 0L;
long q0, q1, q2, q3, q4, q5, q6;
}
我不确定但我猜测这么做是为了防止jdk1.7对padding的优化,额...只能这么猜了,水平实在有限,如果你有确切答案了,请告诉我,十分感谢


其实只要加个public就行了。
public final static class VolatileLong2 {
public volatile long value = 0L;
public long q0, q1, q2, q3, q4, q5, q6;
}
2 楼  budairenqin 2014-05-24  
liuInsect 写道
      volatile long p0, p1, p2, p3, p4, p5, p6;  
        public volatile long value = 0L;  
        volatile long q0, q1, q2, q3, q4, q5, q6;  
这里 为什么是 13 * 8 个字节 还不是 (7+1)*8?  8*8 =64不是正好是一个cache line么?

额...一个cache line确实是64
我水平有限,无法准确回答你这个问题,我说下我的测试结果吧
环境mac os,jdk1.7.15
public final static class VolatileLong2 {
// long p0, p1, p2, p3, p4, p5, p6;
public volatile long value = 0L;
long q0, q1, q2, q3, q4, q5, q6;
}
上面的代码,正好填充了一个cache line,但是会被jdk1.7.15优化掉,也就是说padding失效
而下面代码不会被优化掉,padding有效
public final static class VolatileLong2 {
long p0, p1, p2, p3, p4, p5, p6;
public volatile long value = 0L;
long q0, q1, q2, q3, q4, q5, q6;
}
我不确定但我猜测这么做是为了防止jdk1.7对padding的优化,额...只能这么猜了,水平实在有限,如果你有确切答案了,请告诉我,十分感谢
1 楼  liuInsect 2014-05-21  
      volatile long p0, p1, p2, p3, p4, p5, p6;  
        public volatile long value = 0L;  
        volatile long q0, q1, q2, q3, q4, q5, q6;  
这里 为什么是 13 * 8 个字节 还不是 (7+1)*8?  8*8 =64不是正好是一个cache line么?

你可能感兴趣的:(并发-内存模型)