对齐填充Padding(不够64字节用0填充)是用来解决伪共享问题,保证一个缓存行只读取一个数据,避免多个线程竞争同一个缓存行带来性能问题。
伪共享
CPU缓存是由多个缓存行组成的,缓存行是CPU和内存之间交互的最小单元,每个缓存行大小是64个字节,每次CPU读取数据以块为单位64个字节,一次读取一块数据(猜想上下位的数据也是需要的),避免多次交互,一个缓存行可以缓存多个数据(比如:X, Y, Z三个数据),当多个线程情况下,如果线程A修改数据X,线程B需要修改数据Y,但是都处于同一个缓存行,这时候会存在缓存行竞争,如果线程A获取到缓存行,那么线程B缓存行失效,修改数据会失败,如果线程B竞争到缓存行,线程A修改数据会失败,这样会多次请求失败,影响性能,这就是伪共享问题。
对齐填充代码演示
将下面类中数据对象由ValueNoPadding改成ValuePadding,我们会发现执行效率会高很过
public class ShareExample implements Runnable{
public final static long ITERATIONS = 500L * 1000L * 100L;
private int arrayIndex = 0;
private static ValueNoPadding[] longs; //没有填充的对象数组
public ShareExample(final int arrayIndex) {
this.arrayIndex = arrayIndex;
}
public static void main(final String[] args) throws Exception {
for(int i = 1; i < 10; i++){
System.gc();
final long start = System.currentTimeMillis();
runTest(i);
System.out.println(i + " Threads, duration = " + (System.currentTimeMillis() - start));
}
}
private static void runTest(int NUM_THREADS) throws InterruptedException {
Thread[] threads = new Thread[NUM_THREADS];
longs = new ValueNoPadding[NUM_THREADS];
for (int i = 0; i < longs.length; i++) {
longs[i] = new ValueNoPadding();
}
for (int i = 0; i < threads.length; i++) {
threads[i] = new Thread(new ShareExample(i));
}
for (Thread t : threads) {
t.start();
}
for (Thread t : threads) {
t.join();
}
}
@Override
public void run() {
long i = ITERATIONS + 1;
while (0 != --i) {
longs[arrayIndex].value = 0L;
}
}
public final static class ValuePadding {
protected long p1, p2, p3, p4, p5, p6, p7;
protected volatile long value = 0L; //64字节
//64个字节
protected long p9, p10, p11, p12, p13, p14;
protected long p15;
}
//@Contended //java8
public final static class ValueNoPadding {
// protected long p1, p2, p3, p4, p5, p6, p7;
protected volatile long value = 0L; //8个字节. MESI协议,保证缓存一致性.
// protected long p9, p10, p11, p12, p13, p14, p15;
}
}
Java8 @Contended
除了对字段进行填充之外,还有一个比较清爽的方法,那就是对需要避免陷入伪共享的字段进行注解。Java8中JEP142引入了@Contended注解,被这个注解修饰的字段必须和其他字段放在不同的位置,避免出现伪共享,其实原理也是实现了对齐填充,不过前提是需要通过JVM参数-XX:-RestrictContended开启此功能。
Idea中配置开启@Contented:
1) idea工具栏中 Help --> Edit Custom Vm Options
2) -XX参数中 boolean类型, -XX:+RestrictContended为该属性设置为true,-XX:-RestrictContended为属性设置为false;
3) 上述参数为JVM虚拟机启动时使用;
package com.lucifer.thread.padding;
import sun.misc.Contended;
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;
}
}
public static class VolatileLong {
@Contended
public volatile long value = 0L;
}
}