java 多线程写缓存,Java多线程_缓存对齐
1.什么是緩存對(duì)齊
當(dāng)前的電腦中,數(shù)據(jù)存儲(chǔ)在磁盤上,可以斷電保存,但是讀取效率較低。不斷電的情況下,數(shù)據(jù)可以在內(nèi)存中存儲(chǔ),相對(duì)硬盤效率差不多是磁盤的一萬倍左右。但是運(yùn)算時(shí),速度最快的是直接緩存在CPU中的數(shù)據(jù)。CPU有三級(jí)緩存分別是L1,L2,L3三級(jí),CPU訪問速度大概是內(nèi)存的100倍。
1.1CPU結(jié)構(gòu)
對(duì)于一臺(tái)電腦,其主板可以支持多少個(gè)CPU插槽,稱為CPU個(gè)數(shù)。對(duì)于一顆多核CPU,單片CPU上集成的處理核心稱為CPU核數(shù)。對(duì)于每個(gè)核心,可以給每個(gè)核設(shè)置兩組寄存器,兩組pc。
CPU結(jié)構(gòu)如上圖所示(圖片來自網(wǎng)絡(luò)),對(duì)于一塊CPU,可以有多個(gè)處理核心。每個(gè)核心內(nèi)有自己的L1,L2緩存,多個(gè)核心共用同一個(gè)L3緩存。但一個(gè)電腦如果有多個(gè)CPU插槽,各個(gè)CPU有自己的L3。對(duì)于一個(gè)CPU核心來說,每個(gè)核心都有ALU,邏輯運(yùn)算單元。負(fù)責(zé)對(duì)指令進(jìn)行計(jì)算。Register 寄存器,記錄線程執(zhí)行對(duì)應(yīng)的數(shù)據(jù)。PC:指令寄存器,記錄線程執(zhí)行到了哪個(gè)位置。里面存的是指令行數(shù)。通俗講,就是記錄線程執(zhí)行到了哪一行指令(代碼在進(jìn)入CPU運(yùn)行前,會(huì)被編譯成指令)了。
線程在執(zhí)行的時(shí)候,將當(dāng)前線程對(duì)應(yīng)的數(shù)據(jù)放入寄存器,將執(zhí)行行數(shù)放到指令寄存器,然后執(zhí)行過一個(gè)時(shí)間片后,如果線程沒有執(zhí)行完,將數(shù)據(jù)和指令保存,然后其他線程進(jìn)入執(zhí)行。一個(gè)ALU對(duì)應(yīng)多個(gè)PC|registers的時(shí)候(所謂的四核八線程)。一般來說,同一個(gè)CPU核在同一個(gè)時(shí)間點(diǎn),只能執(zhí)行同一個(gè)線程,但是,如果一個(gè)核里面有兩組寄存器,兩個(gè)pc。那么就可以同時(shí)執(zhí)行兩組線程,在切換線程的時(shí)候,沒必要再去等待寄存器的數(shù)據(jù)保存和數(shù)據(jù)載入。直接切換到下一組寄存器就可以。這就是超線程。
1.2緩存對(duì)齊
CPU到內(nèi)存之間有很多層的內(nèi)存,如圖所示,CPU需要經(jīng)過L1,L2,L3及主內(nèi)存才能讀到數(shù)據(jù)。從主內(nèi)存讀取數(shù)據(jù)時(shí)的過程如下:
當(dāng)我左側(cè)的CPU讀取x的值的時(shí)候,首先會(huì)去L1緩存中去找x的值,如果沒有,那么取L2,L3依次去找。最后從主內(nèi)存讀入的時(shí)候,首先將內(nèi)存數(shù)據(jù)讀入L3,然后L2最后L1,然后再進(jìn)行運(yùn)算。但是讀取的時(shí)候,并不是只讀一個(gè)X的值,而是按塊去讀取(跟電腦的總線寬度有關(guān),一次讀取一塊的數(shù)據(jù),效率更高)。CPU讀取X后,很可能會(huì)用到相鄰的數(shù)據(jù),所以在讀X的時(shí)候,會(huì)把同一塊中的Y數(shù)據(jù)也讀進(jìn)來。這樣在用Y的時(shí)候,直接從L1中取數(shù)據(jù)就可以了。
讀取的塊就叫做緩存行,cache line 。緩存行越大,局部性空間效率越高,但讀取時(shí)間慢。緩存行越小,局部性空間效率越低,但讀取時(shí)間快。目前多取一個(gè)平衡的值,64字節(jié)。
然后,如果你的X和y在同一塊緩存行中,且兩個(gè)字段都用volatile修飾了,那么將來兩個(gè)線程再修改的時(shí)候,就需要將x和y發(fā)生修改的消息高速另外一個(gè)線程,讓它重新加載對(duì)應(yīng)緩存,然而另外一個(gè)線程并沒有使用該緩存行中對(duì)應(yīng)的內(nèi)容,只是因?yàn)榫彺嫘凶x取的時(shí)候跟變量相鄰,這就會(huì)產(chǎn)生效率問題。
解決起來也簡單,我們將數(shù)據(jù)中的兩個(gè)volatile之間插入一些無用的內(nèi)存,將第二個(gè)值擠出當(dāng)前緩存行,那么執(zhí)行的時(shí)候,就不會(huì)出現(xiàn)相應(yīng)問題了。提高代碼效率。
2.緩存對(duì)齊在java中實(shí)現(xiàn)
在java中,jdk一些涉及到多線程的類,有時(shí)候會(huì)看到類似于public volatile long p1,p2,p3,p4,p5,p6,p7;這樣的代碼,有的就是做的緩存行對(duì)齊。
我們?cè)O(shè)計(jì)一個(gè)實(shí)驗(yàn)去驗(yàn)證緩存行對(duì)齊的導(dǎo)致的性能問題,及相關(guān)的解決后的效率問題。具體代碼見第三小節(jié)。這里的思路是,首先,我們寫一個(gè)類T,這個(gè)類里面有一個(gè)用volatile修飾的long屬性的值,這個(gè)值占用8個(gè)字節(jié)。然后聲明一個(gè)靜態(tài)數(shù)組,包含兩個(gè)元素,分別T的兩個(gè)對(duì)象。然后開啟兩個(gè)線程,讓兩個(gè)線程分別給數(shù)組的第一個(gè)值和第二個(gè)值賦值,執(zhí)行一百萬次,看執(zhí)行的耗時(shí)。
這個(gè)時(shí)候,代碼執(zhí)行的時(shí)候如1.2的圖中所示,假設(shè)數(shù)組中第一個(gè)值為X,第二個(gè)值為Y。左側(cè)框內(nèi)為第一個(gè)線程,執(zhí)行修改X值的操作,右側(cè)框內(nèi)為第二個(gè)線程,修改Y的值。因?yàn)閮蓚€(gè)值在同一個(gè)緩存行中,所以在X值在讀取的時(shí)候,同時(shí)將X值和Y值一起讀入緩存。第二個(gè)線程只修改Y的值,但是同樣將XY全部讀入緩存。線程1中X值發(fā)生修改后,第二個(gè)線程中的X值需要進(jìn)行更新。而線程2修改Y的值后也需要同樣的操作,但是這個(gè)更新不是必要的,而且會(huì)影響執(zhí)行的效率。
解決方法是:我們給第T的long值之前加入8個(gè)long值,這樣Y值就會(huì)被擠到其他緩存行,這樣彼此修改的時(shí)候就不會(huì)產(chǎn)生干擾,提高代碼執(zhí)行效率。
下面是具體驗(yàn)證的代碼,其中在沒有加入父類的時(shí)候,是相互干擾時(shí)的執(zhí)行耗時(shí)。第二個(gè)是加入父類后,不再干擾時(shí)的耗時(shí),執(zhí)行后可以看出,第二套代碼在執(zhí)行的時(shí)候,代碼要優(yōu)于第一套代碼的執(zhí)行。
3.緩存對(duì)齊的代碼實(shí)現(xiàn)
1 public classT01_CacheLinePadding {2 private static classT{3 public long x = 0L;4 }5 public static T[] orr = new T[2];6 static{7 orr[0]= newT();8 orr[1]= newT();9 }10 public static void main(String[] args) throwsException {11 Thread t1 = new Thread(()->{12 for (long i = 0; i < 1000_000L; i++) {13 orr[0].x =i;14 }15 });16 Thread t2 = new Thread(()->{17 for (long i = 0; i < 1000_000L; i++) {18 orr[1].x =i;19 }20 });21 final long start =System.nanoTime();22 t1.start();23 t2.start();24 t1.join();25 t2.join();26 System.out.println((System.nanoTime()-start)/100_000);27 }28 }
1 packagemsb;2 /**
3 * 緩存行對(duì)齊問題代碼4 *@authorL Ys5 *6 */
7 public classT02_CacheLinePadding {8 private static classPadding{9 public longp1,p2,p3,p4,p5,p6,p7;10 }11 private static class T extendsPadding{12 public volatile long x = 0L;13 }14 public static T[] orr = new T[2];15 static{16 orr[0]= newT();17 orr[1]= newT();18 }19 public static void main(String[] args) throwsException {20 Thread t1 = new Thread(()->{21 for (long i = 0; i < 1000_000L; i++) {22 orr[0].x =i;23 }24 });25 Thread t2 = new Thread(()->{26 for (long i = 0; i < 1000_000L; i++) {27 orr[1].x =i;28 }29 });30 final long start =System.nanoTime();31 t1.start();32 t2.start();33 t1.join();34 t2.join();35 System.out.println((System.nanoTime()-start)/100_000);36 }37 }
程序員燈塔
轉(zhuǎn)載請(qǐng)注明原文鏈接:Java多線程_緩存對(duì)齊
總結(jié)
以上是生活随笔為你收集整理的java 多线程写缓存,Java多线程_缓存对齐的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: java instantiation,I
- 下一篇: java resize_OpenCV3