程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> JAVA編程 >> JAVA綜合教程 >> Java8的偽共享和緩存行填充--@Contended注釋,java8--@contended

Java8的偽共享和緩存行填充--@Contended注釋,java8--@contended

編輯:JAVA綜合教程

Java8的偽共享和緩存行填充--@Contended注釋,java8--@contended


在我的前一篇文章<偽共享和緩存行填充,從Java 6, Java 7 到Java 8>中, 我們演示了在Java 8中,可以采用@Contended在類級別上的注釋,來進行緩存行填充。這樣,多線程情況下的偽共享沖突問題。 感興趣的同學可以查看該文。

 

其實,@Contended注釋還可以應用於字段級別(Field-Level),當應用於字段級別時,被注釋的字段將和其他字段隔離開來,會被加載在獨立的緩存行上。在字段級別上,@Contended還支持一個“contention group”屬性(Class-Level不支持),同一group的字段們在內存上將是連續,但和其他他字段隔離開來。

 

上面只是泛泛的介紹一下。關於@Contended應用於Field-Level特別是contention group的相關的資料很少,源代碼中的注釋中有一些,還有關於JEP-142(即關於增加@Contended的提議)的郵件討論組中的描述(http://mail.openjdk.java.net/pipermail/hotspot-dev/2012-November/007309.html),其中的講解是非常詳細的(由於該討論發生在@Contended實現的最初階段,不能保證和現在的實現完全一致), 我摘錄和翻譯如下:

 

@Contended注釋的行為如下所示:

A,在類上應用Contended:

@Contended
    public static class ContendedTest2 {
        private Object plainField1;
        private Object plainField2;
        private Object plainField3;
        private Object plainField4;
    }

 

將使整個字段塊的兩端都被填充:(以下是使用 –XX:+PrintFieldLayout的輸出)(翻譯注:注意前面的@140表示字段在類中的地址偏移)

TestContended$ContendedTest2: field layout
    Entire class is marked contended
     @140 --- instance fields start ---
     @140 "plainField1" Ljava.lang.Object;
     @144 "plainField2" Ljava.lang.Object;
     @148 "plainField3" Ljava.lang.Object;
     @152 "plainField4" Ljava.lang.Object;
     @288 --- instance fields end ---
     @288 --- instance ends ---

 

注意,我們使用了128bytes的填充 -- 2倍於大多數硬件緩存行的大小 -- 來避免相鄰扇區預取導致的偽共享沖突。

 

B,在字段上應用Contended:

public static class ContendedTest1 {
        @Contended
        private Object contendedField1;
        private Object plainField1;
        private Object plainField2;
        private Object plainField3;
        private Object plainField4;
    }

 

將導致該字段從連續的字段塊中分離開來並高效的添加填充:

TestContended$ContendedTest1: field layout
     @ 12 --- instance fields start ---
     @ 12 "plainField1" Ljava.lang.Object;
     @ 16 "plainField2" Ljava.lang.Object;
     @ 20 "plainField3" Ljava.lang.Object;
     @ 24 "plainField4" Ljava.lang.Object;
     @156 "contendedField1" Ljava.lang.Object; (contended, group = 0)
     @288 --- instance fields end ---
     @288 --- instance ends ---

 

C, 注解多個字段使他們分別被填充:

public static class ContendedTest4 {
        @Contended
        private Object contendedField1;

        @Contended
        private Object contendedField2;

        private Object plainField3;
        private Object plainField4;
    }

 

被注解的2個字段都被獨立地填充:

TestContended$ContendedTest4: field layout
     @ 12 --- instance fields start ---
     @ 12 "plainField3" Ljava.lang.Object;
     @ 16 "plainField4" Ljava.lang.Object;
     @148 "contendedField1" Ljava.lang.Object; (contended, group = 0)
     @280 "contendedField2" Ljava.lang.Object; (contended, group = 0)
     @416 --- instance fields end ---
     @416 --- instance ends ---

 

在有些cases中,你會想對字段進行分組,同一組的字段會和其他字段有訪問沖突,但是和同一組的沒有。例如,(同一個線程的)代碼同時更新2個字段是很常見的情況。如果同時把2個字段都添加@Contended注解是足夠的(翻譯注:但是太足夠了),但我們可以通過去掉他們之間的填充,來優化它們的內存空間占用。為了區分組,我們有一個參數“contention group”來描述:

所以:

public static class ContendedTest5 {
        @Contended("updater1")
        private Object contendedField1;

        @Contended("updater1")
        private Object contendedField2;

        @Contended("updater2")
        private Object contendedField3;

        private Object plainField5;
        private Object plainField6;
    }

 

內存布局是:

TestContended$ContendedTest5: field layout
     @ 12 --- instance fields start ---
     @ 12 "plainField5" Ljava.lang.Object;
     @ 16 "plainField6" Ljava.lang.Object;
     @148 "contendedField1" Ljava.lang.Object; (contended, group = 12)
     @152 "contendedField2" Ljava.lang.Object; (contended, group = 12)
     @284 "contendedField3" Ljava.lang.Object; (contended, group = 15)
     @416 --- instance fields end ---
     @416 --- instance ends ---

 

注意$contendedField1 和$contendedField2和其他字段之間有填充,但是它們之間是緊挨著的。
 
 
以上是對郵件組中大牛們原始實現解釋的翻譯。
 
下面我們來做一個測試,看@Contended在字段級別,並且帶分組的情況下,是否能解決偽緩存問題。
import sun.misc.Contended;

public class VolatileLong {
    @Contended("group0")
    public volatile long value1 = 0L;  
    @Contended("group0")
    public volatile long value2 = 0L;  
    
    @Contended("group1")
    public volatile long value3 = 0L;  
    @Contended("group1")
    public volatile long value4 = 0L;  
}

 

我們用2個線程來修改字段--

測試1:線程0修改value1和value2;線程1修改value3和value4;他們都在同一組中。

測試2:線程0修改value1和value3;線程1修改value2和value4;他們在不同組中。

 
測試1:
public final class FalseSharing implements Runnable {
    public final static long ITERATIONS = 500L * 1000L * 1000L;
    private static VolatileLong volatileLong;
    private String groupId;

    public FalseSharing(String groupId) {
        this.groupId = groupId;

    }

    public static void main(final String[] args) throws Exception {
        // Thread.sleep(10000);
        System.out.println("starting....");

        volatileLong = new VolatileLong();
        final long start = System.nanoTime();
        runTest();
        System.out.println("duration = " + (System.nanoTime() - start));
    }

    private static void runTest() throws InterruptedException {
        Thread t0 = new Thread(new FalseSharing("t0"));
        Thread t1 = new Thread(new FalseSharing("t1"));
        t0.start();
        t1.start();
        t0.join();
        t1.join();
    }

    public void run() {
        long i = ITERATIONS + 1;
        if (groupId.equals("t0")) {
            while (0 != --i) {
                volatileLong.value1 = i;
                volatileLong.value2 = i;
            }
        } else if (groupId.equals("t1")) {
            while (0 != --i) {
                volatileLong.value3 = i;
                volatileLong.value4 = i;
            }
        }
    }
}
 
測試2:(基於以上代碼修改下面的部分)
public void run() {
        long i = ITERATIONS + 1;
        if (groupId.equals("t0")) {
            while (0 != --i) {
                volatileLong.value1 = i;
                volatileLong.value3 = i;
            }
        } else if (groupId.equals("t1")) {
            while (0 != --i) {
                volatileLong.value2 = i;
                volatileLong.value4 = i;
            }
        }
    }

 

測試1:

starting....
duration = 16821484056

測試2:

starting....
duration = 39191867777

 

可以看出,如果同一線程修改的是同一“contention group”中的字段,沒有偽共享沖突,比有偽共享沖突的情況要快1倍多。

 

後記:

測試3:不使用@Contended

public class VolatileLong {
    public volatile long value1 = 0L;  
    public volatile long value2 = 0L;  
    public volatile long value3 = 0L;  
    public volatile long value4 = 0L;  
}

結果:

starting....
duration = 38096777198

 

 

參考:

http://grepcode.com/file/repository.grepcode.com/java/root/jdk/openjdk/8-b132/sun/misc/Contended.java

http://openjdk.java.net/jeps/142

http://mail.openjdk.java.net/pipermail/hotspot-dev/2012-November/007309.html

  1. 上一頁:
  2. 下一頁:
Copyright © 程式師世界 All Rights Reserved