什么是伪共享html
关于伪共享讲解最清楚的是这篇文章《剖析Disruptor:为何会这么快?(三)伪共享》,我这里就直接摘抄其对伪共享的解释:java
缓存系统中是以缓存行(cache line)为单位存储的。缓存行是2的整数幂个连续字节,通常为32-256个字节。最多见的缓存行大小是64个字节。当多线程修改互相独立的变量时,如 果这些变量共享同一个缓存行,就会无心中影响彼此的性能,这就是伪共享。缓存行上的写竞争是运行在SMP系统中并行线程实现可伸缩性最重要的限制因素。有 人将伪共享描述成无声的性能杀手,由于从代码中很难看清楚是否会出现伪共享。程序员
为了让可伸缩性与线程数呈线性关系,就必须确保不会有两个线程往同一个变量或缓存行中写。两个线程写同一个变量能够在代码中发现。为了肯定互相独立的变量 是否共享了同一个缓存行,就须要了解内存布局,或找个工具告诉咱们。Intel VTune就是这样一个分析工具。本文中我将解释Java对象的内存布局以及咱们该如何填充缓存行以免伪共享。算法
图1说明了伪共享的问题。在核心1上运行的线程想更新变量X,同时核心2上的线程想要更新变量Y。不幸的是,这两个变量在同一个缓存行中。每一个线程都要去 竞争缓存行的全部权来更新变量。若是核心1得到了全部权,缓存子系统将会使核心2中对应的缓存行失效。当核心2得到了全部权而后执行更新操做,核心1就要 使本身对应的缓存行失效。这会来来回回的通过L3缓存,大大影响了性能。若是互相竞争的核心位于不一样的插槽,就要额外横跨插槽链接,问题可能更加严重。缓存
JAVA 7下的方案多线程
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
|
public final class FalseSharing implements Runnable {
public static int NUM_THREADS = 4 ; // change
public final static long ITERATIONS = 500L * 1000L * 1000L;
private final int arrayIndex;
private static VolatileLong[] longs;
public FalseSharing( final int arrayIndex) {
this .arrayIndex = arrayIndex;
}
public static void main( final String[] args) throws Exception {
Thread.sleep( 10000 );
System.out.println( "starting...." );
if (args.length == 1 ) {
NUM_THREADS = Integer.parseInt(args[ 0 ]);
}
longs = new VolatileLong[NUM_THREADS];
for ( int i = 0 ; i < longs.length; i++) {
longs[i] = new VolatileLong();
}
final 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;
}
}
}
|
1
2
3
|
public class VolatileLongPadding {
public volatile long p1, p2, p3, p4, p5, p6; // 注释
}
|
1
2
3
|
public class VolatileLong extends VolatileLongPadding {
public volatile long value = 0L;
}
|
把padding放在基类里面,能够避免优化。(这好像没有什么道理好讲的,JAVA7的内存优化算法问题,能绕则绕)。不过,这种办法怎么看都有点烦,借用另一个博主的话:作个java程序员真难。工具
JAVA 8下的方案布局
在JAVA 8中,缓存行填充终于被JAVA原生支持了。JAVA 8中添加了一个@Contended的注解,添加这个的注解,将会在自动进行缓存行填充。以上的例子能够改成:性能
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
|
public final class FalseSharing implements Runnable {
public static int NUM_THREADS = 4 ; // change
public final static long ITERATIONS = 500L * 1000L * 1000L;
private final int arrayIndex;
private static VolatileLong[] longs;
public FalseSharing( final int arrayIndex) {
this .arrayIndex = arrayIndex;
}
public static void main( final String[] args) throws Exception {
Thread.sleep( 10000 );
System.out.println( "starting...." );
if (args.length == 1 ) {
NUM_THREADS = Integer.parseInt(args[ 0 ]);
}
longs = new VolatileLong[NUM_THREADS];
for ( int i = 0 ; i < longs.length; i++) {
longs[i] = new VolatileLong();
}
final 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;
}
}
}
|
1
2
3
4
5
6
|
import sun.misc.Contended;
@Contended
public class VolatileLong {
public volatile long value = 0L;
}
|
执行时,必须加上虚拟机参数-XX:-RestrictContended,@Contended注释才会生效。不少文章把这个漏掉了,那样的话实际上就没有起做用。优化
原文连接:http://www.cnblogs.com/Binhua-Liu/p/5620339.html