在個人前一篇文章<僞共享和緩存行填充,從Java 6, Java 7 到Java 8>中, 咱們演示了在Java 8中,能夠採用@Contended在類級別上的註釋,來進行緩存行填充。這樣,多線程狀況下的僞共享衝突問題。 感興趣的同窗能夠查看該文。html
其實,@Contended註釋還能夠應用於字段級別(Field-Level),當應用於字段級別時,被註釋的字段將和其餘字段隔離開來,會被加載在獨立的緩存行上。在字段級別上,@Contended還支持一個「contention group」屬性(Class-Level不支持),同一group的字段們在內存上將是連續,但和其餘他字段隔離開來。java
上面只是泛泛的介紹一下。關於@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:this
public static class ContendedTest1 { @Contended private Object contendedField1; private Object plainField1; private Object plainField2; private Object plainField3; private Object plainField4; }
將致使該字段從連續的字段塊中分離開來並高效的添加填充:spa
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, 註解多個字段使他們分別被填充:.net
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://openjdk.java.net/jeps/142
http://mail.openjdk.java.net/pipermail/hotspot-dev/2012-November/007309.html