<output id="qn6qe"></output>

    1. <output id="qn6qe"><tt id="qn6qe"></tt></output>
    2. <strike id="qn6qe"></strike>

      亚洲 日本 欧洲 欧美 视频,日韩中文字幕有码av,一本一道av中文字幕无码,国产线播放免费人成视频播放,人妻少妇偷人无码视频,日夜啪啪一区二区三区,国产尤物精品自在拍视频首页,久热这里只有精品12

      java并發(fā)性能陷阱--偽共享

      緩存可以說(shuō)是計(jì)算機(jī)領(lǐng)域最偉大的發(fā)明之一,
      經(jīng)常會(huì)有人問(wèn),緩存是越多越好么?
      一般人們都會(huì)斬釘截鐵的回答不是。
      至于為什么?
      往往無(wú)法直覺(jué)回答了,可能會(huì)從緩存一致性,空間占用等幾個(gè)角度逐一分析。
      今天就來(lái)看看由于一致性導(dǎo)致的緩存問(wèn)題。
      在之前的文章中,我們聊過(guò)JMM java的內(nèi)存模型(一定要有所了解,不太清楚的同學(xué)可以看下前文鏈接http://www.rzrgm.cn/jilodream/p/9452391.html),可以知道線(xiàn)程并不是直接讀寫(xiě)內(nèi)存,而是調(diào)用線(xiàn)程自己的工作空間。
      但這只是一個(gè)邏輯模型,線(xiàn)程我們可以理解為cpu的核心,工作空間所對(duì)應(yīng)的位置一般是指cpu的緩存。就像下圖這樣:

      jmmcpu

      目前主流的cpu就是每個(gè)核心有自己的多級(jí)緩存,一般還會(huì)加一個(gè)共享緩存,

      越靠近核,緩存越小,但越快,成本也越大。
      java線(xiàn)程實(shí)際對(duì)應(yīng)的就是這個(gè)核,工作空間對(duì)應(yīng)的就是這個(gè)緩存。
      如果你詳細(xì)思考,就會(huì)考慮到緩存中的數(shù)據(jù)時(shí)如何加載變量的。畢竟變量又長(zhǎng)有短,如何加載定位的?
      一般來(lái)說(shuō),我們將內(nèi)存劃分成若干的塊,(防盜連接:本文首發(fā)自http://www.rzrgm.cn/jilodream/ )每一塊是64個(gè)字節(jié)(主流是這個(gè)大?。?/span>
      同時(shí)我們將緩存劃分成若干的緩存行,也是64個(gè)字節(jié)。
      cpu每次加載時(shí),不是按照某個(gè)變量加載,而是將已經(jīng)劃分好的整塊內(nèi)容直接加載到緩存行中。因?yàn)閺臄?shù)據(jù)的使用經(jīng)驗(yàn)來(lái)看,一般我們?cè)谑褂媚硞€(gè)變量時(shí),很大可能會(huì)使用鄰近變量,這種緩存的預(yù)判加載,提高了緩存的命中率。

      有小伙伴會(huì)有疑問(wèn),會(huì)不會(huì)不同核的緩存行加載的數(shù)據(jù)跨了內(nèi)存塊了,也就是A核的緩存行是 xyz變量,B核的緩存行是yza變量。這是不會(huì)的,緩存塊是根據(jù)內(nèi)存的地址和偏移量劃分好的,不會(huì)根據(jù)不同核來(lái)劃分不同的邊界的。

      cacheline1

      做過(guò)緩存設(shè)計(jì)的同學(xué)肯定知道,在設(shè)計(jì)時(shí)一定要考慮數(shù)據(jù)一致性的問(wèn)題。如果多份緩存以及主存之間的數(shù)據(jù)不一致,就無(wú)法并發(fā)處理,無(wú)法得到準(zhǔn)確的結(jié)果。

      (ps,cpu一般是通過(guò)MESI緩存一致性協(xié)議并且配合失效緩存隊(duì)列等等來(lái)實(shí)現(xiàn)的,感興趣的讀者可以查下相關(guān)內(nèi)容)
      從前文中的java內(nèi)存模型中可以知道,當(dāng)volatile變量發(fā)生變化時(shí),java通過(guò)內(nèi)存屏障,來(lái)強(qiáng)制失效其它c(diǎn)pu核心中緩存。
      但是在真實(shí)情況下,cpu是按照行來(lái)緩存變量的,而不是單個(gè)變量,此時(shí)標(biāo)記失效的就是整個(gè)緩存行。那么就會(huì)出現(xiàn)類(lèi)似一個(gè)情況:
      線(xiàn)程1操作變量a,線(xiàn)程2操作變量b,根據(jù)緩存的加載機(jī)制
      (1)兩者的均加載同一段緩存行。
      (2)當(dāng)線(xiàn)程1 修改完變量a時(shí),通知其它線(xiàn)程失效該緩存行
      (3)線(xiàn)程2修改變量b,發(fā)現(xiàn)緩存行失效,重新加載緩存行,修改完變量b后,重新知會(huì)其它線(xiàn)程該行已經(jīng)失效
      這樣當(dāng)線(xiàn)程1每次修改變量a,線(xiàn)程2每次修改變量b時(shí),當(dāng)前緩存都不斷的需要重新加載,本質(zhì)上已經(jīng)失去了緩存的意義,還增加了緩存狀態(tài)控制,緩存重新加載的開(kāi)銷(xiāo)。
      這種在相同的緩存行的多個(gè)變量,但是由于并發(fā)原因,導(dǎo)致緩存不斷失效,無(wú)法利用緩存讀取變量的場(chǎng)景,我們就稱(chēng)之為偽共享。(False Sharing)

      cacheline2

      這種情況其實(shí)不僅僅是java,其它語(yǔ)言,甚至是多緩存的業(yè)務(wù),都會(huì)有類(lèi)似的問(wèn)題。

      即由于并發(fā)引起的緩存聯(lián)動(dòng)失效,即使對(duì)我當(dāng)前業(yè)務(wù)沒(méi)有實(shí)際影響,但是由于緩存一致性的協(xié)議設(shè)計(jì),我們判斷當(dāng)前緩存已經(jīng)臟了。我們就需要重新加載。緩存的優(yōu)勢(shì)喪失,成本卻被無(wú)限放大。
      就像下邊這個(gè)例子:

      緩存類(lèi):

      1 public class CacheA {
      2     volatile int  a;
      3 
      4     volatile int  b;
      5 }

      線(xiàn)程類(lèi):

       1 public class Main {
       2     private static CacheA cache = new CacheA();
       3     private static final int TOTAL = 1000000;
       4 
       5     public static void main(String[] args) {
       6         Runnable r1 = new Runnable() {
       7             @Override
       8             public void run() {
       9                 long startTime = System.currentTimeMillis();
      10                 for (int i = 0; i < TOTAL; i++) {
      11                     cache.a = (i-99999)*(i+99999);
      12                 }
      13                 long endTime = System.currentTimeMillis();   // 結(jié)束時(shí)間(毫秒)
      14                 long cost = endTime - startTime;         // 耗時(shí)(毫秒)
      15 
      16                 System.out.println("方法耗時(shí)1: " + cost + " 毫秒");
      17             }
      18         };
      19 
      20         Runnable r2 = new Runnable() {
      21             @Override
      22             public void run() {
      23                 long startTime = System.currentTimeMillis();
      24                 for (int i = 0; i < TOTAL; i++) {
      25                     cache.b =(i-99999)*(i+99999);
      26                 }
      27                 long endTime = System.currentTimeMillis();   // 結(jié)束時(shí)間(毫秒)
      28                 long cost = endTime - startTime;         // 耗時(shí)(毫秒)
      29 
      30                 System.out.println("方法耗時(shí)2: " + cost + " 毫秒");
      31             }
      32         };
      33 
      34         Thread t1=new Thread(r1);
      35         t1.start();
      36 
      37         Thread t2=new Thread(r2);
      38         t2.start();
      39     }
      40 }

      代碼邏輯是兩個(gè)線(xiàn)程并發(fā)修改兩個(gè)變量,這兩個(gè)變量在同一個(gè)實(shí)例里邊。

      輸出結(jié)果是這樣的:

      Connected to the target VM, address: '127.0.0.1:58237', transport: 'socket'
      方法耗時(shí)2: 19 毫秒
      方法耗時(shí)1: 22 毫秒
      Disconnected from the target VM, address: '127.0.0.1:58237', transport: 'socket'
      
      Process finished with exit code 0

      我們來(lái)修改代碼,加上很多無(wú)效的變量,重新執(zhí)行,

      緩存類(lèi):

       1 public class CacheB {
       2     volatile int  a;
       3     long temp1=0;
       4     long temp2=0;
       5     long temp3=0;
       6     long temp4=0;
       7     long temp5=0;
       8     long temp6=0;
       9     long temp7=0;
      10 
      11     volatile int  b;
      12 }

      線(xiàn)程類(lèi):

       1 public class Main {
       2     private static CacheB cache = new CacheB();
       3     private static final int TOTAL = 1000000;
       4 
       5     public static void main(String[] args) {
       6         Runnable r1 = new Runnable() {
       7             @Override
       8             public void run() {
       9                 long startTime = System.currentTimeMillis();
      10                 for (int i = 0; i < TOTAL; i++) {
      11                     cache.a = (i-99999)*(i+99999);
      12                 }
      13                 long endTime = System.currentTimeMillis();   // 結(jié)束時(shí)間(毫秒)
      14                 long cost = endTime - startTime;         // 耗時(shí)(毫秒)
      15 
      16                 System.out.println("方法耗時(shí)1: " + cost + " 毫秒");
      17             }
      18         };
      19 
      20         Runnable r2 = new Runnable() {
      21             @Override
      22             public void run() {
      23                 long startTime = System.currentTimeMillis();
      24                 for (int i = 0; i < TOTAL; i++) {
      25                     cache.b =(i-99999)*(i+99999);
      26                 }
      27                 long endTime = System.currentTimeMillis();   // 結(jié)束時(shí)間(毫秒)
      28                 long cost = endTime - startTime;         // 耗時(shí)(毫秒)
      29 
      30                 System.out.println("方法耗時(shí)2: " + cost + " 毫秒");
      31             }
      32         };
      33 
      34         Thread t1=new Thread(r1);
      35         t1.start();
      36 
      37         Thread t2=new Thread(r2);
      38         t2.start();
      39     }
      40 }

      執(zhí)行結(jié)果如下:

      Connected to the target VM, address: '127.0.0.1:58389', transport: 'socket'
      方法耗時(shí)1: 10 毫秒
      方法耗時(shí)2: 10 毫秒
      Disconnected from the target VM, address: '127.0.0.1:58389', transport: 'socket'
      
      Process finished with exit code 0

      是不是很神奇,我們給一個(gè)對(duì)象加了很多無(wú)用的變量,它居然變快了。而且性能還提升了不少。
      這個(gè)優(yōu)化的核心思路就是通過(guò)強(qiáng)制指定內(nèi)存相對(duì)位置,將不相關(guān)的變量強(qiáng)制分配到不同的緩存行上,讓緩存行不會(huì)因?yàn)楫?dāng)前不使用的緩存而被強(qiáng)制失效。
      很多人也喜歡這樣子寫(xiě):

          private volatile long value;
          private long p1, p2, p3, p4, p5, p6, p7;

      通過(guò)手動(dòng)補(bǔ)齊剩余字節(jié),確保當(dāng)前變量盡可能在一個(gè)緩存行上。
      但是這樣子寫(xiě)代碼就很不方便了,(防盜連接:本文首發(fā)自http://www.rzrgm.cn/jilodream/ )我們要增加很多無(wú)意義的字段,或者通過(guò)其它變量穿插起來(lái)。很容易被別人誤改,誤刪,也影響代碼最重要的閱讀性。
      因此java在8及以上的版本,增加了一個(gè)注解@Contended
      Contended
      美[k?n?tend] 英[k?n'tend]
      v.競(jìng)爭(zhēng);認(rèn)為;爭(zhēng)奪

      這個(gè)注解既可以用在類(lèi)上,也可以用在變量上代碼如下:

      緩存類(lèi):

       1 import jdk.internal.vm.annotation.Contended;
       2 
       3 /**
       4  * @discription
       5  */
       6 public class CacheC {
       7     @Contended
       8     volatile int  a;
       9 
      10     @Contended
      11     volatile int  b;
      12 }

      線(xiàn)程執(zhí)行類(lèi):

       1 public class Main {
       2     private static CacheC cache = new CacheC();
       3     private static final int TOTAL = 1000000;
       4 
       5     public static void main(String[] args) {
       6         Runnable r1 = new Runnable() {
       7             @Override
       8             public void run() {
       9                 long startTime = System.currentTimeMillis();
      10                 for (int i = 0; i < TOTAL; i++) {
      11                     cache.a = (i-99999)*(i+99999);
      12                 }
      13                 long endTime = System.currentTimeMillis();   // 結(jié)束時(shí)間(毫秒)
      14                 long cost = endTime - startTime;         // 耗時(shí)(毫秒)
      15 
      16                 System.out.println("方法耗時(shí)1: " + cost + " 毫秒");
      17             }
      18         };
      19 
      20         Runnable r2 = new Runnable() {
      21             @Override
      22             public void run() {
      23                 long startTime = System.currentTimeMillis();
      24                 for (int i = 0; i < TOTAL; i++) {
      25                     cache.b =(i-99999)*(i+99999);
      26                 }
      27                 long endTime = System.currentTimeMillis();   // 結(jié)束時(shí)間(毫秒)
      28                 long cost = endTime - startTime;         // 耗時(shí)(毫秒)
      29 
      30                 System.out.println("方法耗時(shí)2: " + cost + " 毫秒");
      31             }
      32         };
      33 
      34         Thread t1=new Thread(r1);
      35         t1.start();
      36 
      37         Thread t2=new Thread(r2);
      38         t2.start();
      39     }
      40 }

      同時(shí)我們要在jdk 啟動(dòng)時(shí)配上虛擬機(jī)參數(shù):

      -XX:-RestrictContended

      這個(gè)配置參數(shù)表示啟用Contended注解

      同時(shí)IDEA等(防盜連接:本文首發(fā)自http://www.rzrgm.cn/jilodream/ )工具還會(huì)提示我們?cè)谂渲弥虚_(kāi)啟編譯選項(xiàng)開(kāi)關(guān),允許代碼訪(fǎng)問(wèn)jdk內(nèi)部/隱藏的api

      --add-exports java.base/jdk.internal.vm.annotation=ALL-UNNAMED

      使用注解后,執(zhí)行結(jié)果如下

      Connected to the target VM, address: '127.0.0.1:56688', transport: 'socket'
      方法耗時(shí)2: 11 毫秒
      方法耗時(shí)1: 12 毫秒
      Disconnected from the target VM, address: '127.0.0.1:56688', transport: 'socket'

      和手動(dòng)補(bǔ)齊的速度差不多。

      手動(dòng)補(bǔ)齊易于控制,但是影響代碼閱讀,交給虛擬機(jī)自動(dòng)補(bǔ)齊。

      通過(guò)注解交給jvm來(lái)強(qiáng)制填充隔離,又因?yàn)槭莾?nèi)部API,不保證穩(wěn)定性,因此大家根據(jù)自己情況來(lái)選用。

       

      posted @ 2025-10-30 15:42  王若伊_恩賜解脫  閱讀(298)  評(píng)論(0)    收藏  舉報(bào)

      主站蜘蛛池模板: 久久国产成人午夜av影院| 无码人妻精品一区二区三| 蜜臀av黑人亚洲精品| 又大又硬又爽免费视频| 亚洲精品日韩在线观看| 亚洲无人区码二码三码区| 亚洲av无码精品色午夜| 亚洲日韩性欧美中文字幕| 国产精品中出一区二区三区 | 国产乱精品一区二区三区| 欧美成人性色一区欧美成人性色区| 久久精品国产99久久久古代| 性视频一区| 国产人妻精品无码av在线| 亚洲综合一区二区三区视频| 日韩av综合中文字幕| 午夜通通国产精品福利| 国产一精品一av一免费| 国产免费无遮挡吃奶视频| 久九九精品免费视频| 国产精品日韩专区第一页| 国产综合久久99久久| 久久精品国产久精国产| 亚洲av鲁丝一区二区三区黄| 亚洲国模精品一区二区| 最近2019中文字幕大全第二页| 国产一区二区不卡91| 综合偷自拍亚洲乱中文字幕| 中国女人大白屁股ass| 久久涩综合一区二区三区| 激情人妻中出中文字幕一区| 热久久美女精品天天吊色| 亚洲熟女乱色一区二区三区| 精品国产亚洲av麻豆特色| 亚洲无av在线中文字幕| 亚洲热视频这里只有精品| 久久精品人人槡人妻人人玩| 久久青青草原精品国产app| 推油少妇久久99久久99久久| 成人国产乱对白在线观看| 偷偷做久久久久免费网站|