欧美一级特黄大片做受成人-亚洲成人一区二区电影-激情熟女一区二区三区-日韩专区欧美专区国产专区

Java并發(fā)中的內(nèi)存模型

這篇文章主要講解了“Java并發(fā)中的內(nèi)存模型”,文中的講解內(nèi)容簡單清晰,易于學(xué)習(xí)與理解,下面請大家跟著小編的思路慢慢深入,一起來研究和學(xué)習(xí)“Java并發(fā)中的內(nèi)存模型”吧!

網(wǎng)站建設(shè)哪家好,找成都創(chuàng)新互聯(lián)!專注于網(wǎng)頁設(shè)計、網(wǎng)站建設(shè)、微信開發(fā)、成都微信小程序、集團企業(yè)網(wǎng)站建設(shè)等服務(wù)項目。為回饋新老客戶創(chuàng)新互聯(lián)還提供了市中免費建站歡迎大家使用!

CPU和內(nèi)存

在講JMM之前,我想先和大家聊聊硬件層面的東西。大家應(yīng)該都知道執(zhí)行運算操作的CPU本身是不具備存儲能力的,它只負(fù)責(zé)根據(jù)指令對傳遞進(jìn)來的數(shù)據(jù)做相應(yīng)的運算,而數(shù)據(jù)存儲這一任務(wù)則交給內(nèi)存去完成。雖然內(nèi)存的運行速度雖然比起硬盤快非常多,但是和3GHZ,4GHZ,甚至5GHZ的CPU比起來還是太慢了,在CPU的眼中,內(nèi)存運行的速度簡直就是弟弟中的弟弟,等內(nèi)存進(jìn)行一次讀寫操作,CPU能思考成百上千次人生了:grin:。但是CPU的運算能力是緊缺資源啊,可不能這么白白浪費了,所以得想辦法解決這一個問題。

沒有什么問題是一個緩存不能解決的,如果有,那就再加一個緩存 ——魯迅:反正我沒說這句話

所以人們就想到了給CPU增加一個高速緩存(為什么是加高速緩存而不是給內(nèi)存提高速度就牽扯到硬件成本問題了)來解決這個問題,比如像博主用的Intel的I9 9900k CPU就帶了高達(dá)16M的三級緩存,所以硬件上的內(nèi)存模型大概如下圖所示。

Java并發(fā)中的內(nèi)存模型

如圖可以很清楚的看到,在CPU內(nèi)部構(gòu)建了一到多層的緩存,并且其中的L1 Cache是CPU內(nèi)核心獨有的,不同的Core之間是不能共享的,而L2 Cache則是所有的核心共享。簡單來說就是CPU在讀取一個數(shù)據(jù)時會先去最近的Cache層級上讀取,如果找不到則會去下一個層級尋找,都找不到的話就會從內(nèi)存中去加載,而如果Cache中能拿到所需要的數(shù)據(jù)就不會去內(nèi)存讀取。在將數(shù)據(jù)寫回的時候也會先寫入Cache中,等待合適的時機再寫入到內(nèi)存中(其中有一個細(xì)節(jié)就是緩存行的問題,關(guān)于這部分內(nèi)容放在文章結(jié)尾)。而由于存在多個cache層級,并且部分cache還不能夠被共享,所以會存在內(nèi)存可見性的問題。

舉個簡單的例子: 假設(shè)現(xiàn)在存在兩個Core,分別是CoreA和CoreB并且他們都擁有屬于自己的L1Chace和共用的L2Cache。同時有一個變量X的值為1,該變量已經(jīng)被加載在L2Cahce上。此時CoreA執(zhí)行運算需要用到變量X,先去L1Cache尋找,未命中,繼續(xù)在L2Cache尋找,命中成功,將X=1載入L1Cahce,再經(jīng)過一系列運算后將X修改為2并寫入L1Cache。于此同時CoreB剛好也需要X來進(jìn)行運算,此時他去自己的L1Cahce尋找,未命中,繼續(xù)L2Cache尋找,命中成功,將X=1載入自己的L1Cache。此時就出現(xiàn)了問題,CoreA明明已經(jīng)將X的值修改為2了,CoreB讀取到的依然是X=1,這就是內(nèi)存可見性問題。

看到這里的小伙伴們可能要問了,博主你啥情況啊,你這寫的漸漸忘記標(biāo)題了啊,說好了Java內(nèi)存模型,你扯這么多硬件上的問題干啥???(╯‵□′)╯︵┻━┻

Java中的主內(nèi)存和工作內(nèi)存

小伙伴們別著急,其實JMM和上面的硬件層次上的模型很像,不信看下面的圖片

Java并發(fā)中的內(nèi)存模型

怎么樣,是不是看起來很像,可以簡單的理解為線程的工作內(nèi)存就是CPU里Core獨占的L1Cahce,而主內(nèi)存就是共享的L2Cache。所以上述的內(nèi)存一致性問題也會在JMM中存在,而JMM就需要制定一些列的規(guī)則來保證內(nèi)存一致性,這也是Java多線程并發(fā)的一個疑難點,那么JMM制定了哪些規(guī)則呢?

內(nèi)存間交互操作 首先是主內(nèi)存的工作內(nèi)存之間的交互協(xié)議,具體來說定義了以下幾個操作(并且保證這幾個操作都是原子性的):

  • lock (鎖定)作用于主內(nèi)存的變量,將一個變量標(biāo)識為一個線程獨占狀態(tài)

  • unlock(解鎖)作用于主內(nèi)存的變量,將一個處于鎖定狀態(tài)的變量釋放出來,釋放之后才能被其他線程鎖定

  • read(讀?。┳饔糜谥鲀?nèi)存的變量,將一個變量的值從主內(nèi)存?zhèn)鬏數(shù)骄€程工作內(nèi)存中,便于之后的load操作使用

  • load(載入)作用于工作內(nèi)存的變量,它把read操作從主內(nèi)存中得到的變量值放入工作內(nèi)存的變量副本中。

  • use(使用)作用于工作內(nèi)存的變量,把工作內(nèi)存中的一個變量值傳遞給執(zhí)行引擎,每當(dāng)虛擬機遇到一個需要使用變量的值的字節(jié)碼指令時將會執(zhí)行這個操作。

  • assign(賦值)作用于工作內(nèi)存的變量,它把一個從執(zhí)行引擎接收到的值賦值給工作內(nèi)存的變量,每當(dāng)虛擬機遇到一個給變量賦值的字節(jié)碼指令時執(zhí)行這個操作。

  • store(存儲)作用于工作內(nèi)存的變量,把工作內(nèi)存中的一個變量的值傳送到主內(nèi)存中,以便隨后的write的操作。

  • write(寫入)作用于主內(nèi)存的變量,它把store操作從工作內(nèi)存中一個變量的值傳送到主內(nèi)存的變量中。

同時還規(guī)定了執(zhí)行上述八個操作時必須遵循以下規(guī)則:

  • 如果要把一個變量從主內(nèi)存中復(fù)制到工作內(nèi)存,就需要按順尋地執(zhí)行read和load操作, 如果把變量從工作內(nèi)存中同步回主內(nèi)存中,就要按順序地執(zhí)行store和write操作。但Java內(nèi)存模型只要求上述操作必須按順序執(zhí)行,而沒有保證必須是連續(xù)執(zhí)行。

  • 不允許read和load、store和write操作之一單獨出現(xiàn)

  • 不允許一個線程丟棄它的最近assign的操作,即變量在工作內(nèi)存中改變了之后必須同步到主內(nèi)存中。

  • 不允許一個線程無原因地(沒有發(fā)生過任何assign操作)把數(shù)據(jù)從工作內(nèi)存同步回主內(nèi)存中。

  • 一個新的變量只能在主內(nèi)存中誕生,不允許在工作內(nèi)存中直接使用一個未被初始化(load或assign)的變量。即就是對一個變量實施use和store操作之前,必須先執(zhí)行過了assign和load操作。

  • 一個變量在同一時刻只允許一條線程對其進(jìn)行l(wèi)ock操作,但lock操作可以被同一條線程重復(fù)執(zhí)行多次,多次執(zhí)行l(wèi)ock后,只有執(zhí)行相同次數(shù)的unlock操作,變量才會被解鎖。lock和unlock必須成對出現(xiàn)

  • 如果對一個變量執(zhí)行l(wèi)ock操作,將會清空工作內(nèi)存中此變量的值,在執(zhí)行引擎使用這個變量前需要重新執(zhí)行l(wèi)oad或assign操作初始化變量的值

  • 如果一個變量事先沒有被lock操作鎖定,則不允許對它執(zhí)行unlock操作;也不允許去unlock一個被其他線程鎖定的變量。

  • 對一個變量執(zhí)行unlock操作之前,必須先把此變量同步到主內(nèi)存中(執(zhí)行store和write操作)。

(上述部分參考并引用《深入理解Java虛擬機》中的內(nèi)容)

volatile(能夠保證內(nèi)存可見性和禁止指令重排序)

對于volatile修飾的變量,JMM對其有一些特殊的規(guī)定。

內(nèi)存可見性

往簡單來說volatile關(guān)鍵字可以理解為,有一個volatile修飾的變量x,當(dāng)一個線程需要使用該變量的時候,直接從主內(nèi)存中讀取,而當(dāng)一個線程修改該變量的值時,直接寫入到主內(nèi)存中。根據(jù)之前的分析我們能得出具備這些特性的volatile能夠保證一個變量的內(nèi)存可見性和內(nèi)存一致性。

指令重排序

指令重排序是一個大部分CPU都有的操作,同時JVM在運行時也會存在指令重排序的操作。 簡單舉個:chestnut:

      private void test(){        int a,b,c;//1
        a=1;//2
        b=3;//3
        c=a+b;//4
    }

假設(shè)有上面這么一個方法,內(nèi)部有這4行代碼。那么JVM可能會對其進(jìn)行指令重排序,而指令重排序的規(guī)定則是as-if-serial 不管怎么重排序(編譯器和處理器為了提高并行度),(單線程)程序的執(zhí)行結(jié)果不能被改變。根據(jù)這一規(guī)定,編譯器和處理器不會對有依賴關(guān)系的指令重排序,但是對沒有依賴的指令則可能會進(jìn)行重排序。放在上面的例子里面就是,第1行代碼和2,3,4行代碼是有依賴關(guān)系的,所以第一行代碼的指令必須排在2,3,4之前,因為不可能對一個未定義的變量進(jìn)行賦值操作。而第2,3行代碼之間并沒有相互依賴關(guān)系,所以此處可能會發(fā)生指令重排序,先執(zhí)行3,再執(zhí)行2。而最后的第4行代碼和之前的3行代碼都有依賴關(guān)系,所以他一定會放在最后執(zhí)行。

既然JVM特別指出指令重排序只在單線程下和未排序的效果一致,那是否表示在多線程下會存在一些問題呢? 答案是肯定的,多線程下指令重排序會帶來一些意想不到的結(jié)果。

 int a=0;    //flag作為一個標(biāo)識符,標(biāo)識是否寫入完成
    boolean flag = false;    public void writer(){
        a=10;//1
        flag=true;//2
    }    public void reader(){        if (flag)
            System.out.println("a:"+a);
    }

假設(shè)存在一個類,他有上述部分的field和method,該類在設(shè)計上以flag作為寫入是否完成的標(biāo)志,在單線程下這并不會存在問題。而此時有兩個線程分別執(zhí)行writer和reader方法,暫時不考慮內(nèi)存可見性的問題,假設(shè)對a和flag的寫入,是立即被其他線程所知曉的,這個時候大家覺得輸出a的值為多少?10?

即使不考慮內(nèi)存可見性,此時a的值還是有可能會輸出0,這就是指令重排序帶來的問題。在上述代碼中注釋1和2處的代碼是沒有依賴關(guān)系的,在單線程下先執(zhí)行1還是2都沒有任何問題,根據(jù)as-if-serial 原則此時就可能會發(fā)生指令重排序。

而volatile關(guān)鍵字可以禁止指令重排序。

long,double的問題

我們都知道JMM定義的8個主內(nèi)存和工作內(nèi)存之間的操作都是具備原子性的,但是對long和double這兩個64位的數(shù)據(jù)類型有一些例外。

允許虛擬機將沒有被volatile修飾的long和double的64數(shù)據(jù)的讀寫操作劃分為兩次32位的讀寫操作,即不要求虛擬機保證對他們的load ,store,read,write四個操作的原子性。 但是大部分的虛擬機實現(xiàn)都保證了這四個操作的原子性的,所以大部分時候我們都不需要刻意的對long,double對象使用volatile修飾。

性能問題

volatile是Java提供的保證內(nèi)存可見性的最輕量級操作,比起重量級的synchronized能快上不少,但是具體能快多少這部分沒辦法量化。而我們可以知道的是volatile修飾的變量讀操作的性能消耗幾乎和普通變量相差無幾,而寫操作則會慢上一些。所以當(dāng)volatile能解決我們的問題的時候(內(nèi)存可見性和禁止指令重排序),我們應(yīng)該優(yōu)先選擇使用volatile而不是鎖。

synchronized的內(nèi)存語義

簡單概括就是

當(dāng)程序進(jìn)入synchronized塊時,把在synchronized塊中用到的變量從工作內(nèi)存中清楚,這樣在需要訪問這些變量的時候會重新從主內(nèi)存中獲取。當(dāng)程序退出synchronized塊時,把對塊中恭喜變量的修改刷新到主內(nèi)存。 如此依賴synchronized也能保證了內(nèi)存的可見性。

final的內(nèi)存語義

final也能保證內(nèi)存的可見性

被final修飾的字段在構(gòu)造器中一旦初始化完成,并且構(gòu)造器沒有把this引用傳遞出去,那么其他線程中就能看見final字段的值。

后記之CPU緩存行和偽共享

什么是偽共享

根據(jù)前面的文章,我們知道CPU和Memory之間是有Cache的,而Cache內(nèi)部是按行存儲的,行擁有固定的大小,這些行被稱為緩存行。 當(dāng)CPU訪問的某個變量不在Cache中時,就會去內(nèi)存里獲取,并將該變量所在內(nèi)存的一個緩存行大小的數(shù)據(jù)讀入Cache中。由于一次讀取并不是單個對象而是一整個緩存行,所以可能會存在多個變量被讀入一個緩存行中。而一個緩存行只能同時被一個線程操作,所以當(dāng)多個線程同時修改一個緩存行里的多個變量時會造成其他線程等待從而帶來性能損耗(但是在單線程情況下,偽共享反而會提升性能,因為一次性可能會緩存多個變量,節(jié)省后續(xù)變量的讀取時間)。

如何避免偽共享

在Java8之后可以使用JDK提供的@sun.misc.Contended注解來解決偽共享,像Thread中的threadLocalRandom 字段就使用了這個注解。

感謝各位的閱讀,以上就是“Java并發(fā)中的內(nèi)存模型”的內(nèi)容了,經(jīng)過本文的學(xué)習(xí)后,相信大家對Java并發(fā)中的內(nèi)存模型這一問題有了更深刻的體會,具體使用情況還需要大家實踐驗證。這里是創(chuàng)新互聯(lián),小編將為大家推送更多相關(guān)知識點的文章,歡迎關(guān)注!

當(dāng)前名稱:Java并發(fā)中的內(nèi)存模型
本文地址:http://aaarwkj.com/article8/gdsgip.html

成都網(wǎng)站建設(shè)公司_創(chuàng)新互聯(lián),為您提供商城網(wǎng)站、虛擬主機ChatGPT、微信小程序定制網(wǎng)站、網(wǎng)站排名

廣告

聲明:本網(wǎng)站發(fā)布的內(nèi)容(圖片、視頻和文字)以用戶投稿、用戶轉(zhuǎn)載內(nèi)容為主,如果涉及侵權(quán)請盡快告知,我們將會在第一時間刪除。文章觀點不代表本網(wǎng)站立場,如需處理請聯(lián)系客服。電話:028-86922220;郵箱:631063699@qq.com。內(nèi)容未經(jīng)允許不得轉(zhuǎn)載,或轉(zhuǎn)載時需注明來源: 創(chuàng)新互聯(lián)

h5響應(yīng)式網(wǎng)站建設(shè)
久久熟妇少妇亚洲精品| 亚洲伊人成综合人影院| 精品国产自在久久成人| 高清白嫩学生自拍视频| 欧美精品亚洲二区中文乱码| 亚洲中文字幕在线乱码| 日韩午夜电影一区二区三区| 国产一区二区三区午夜视频| 日韩国产精品视频二区| 久久精品久久黄色片看看| av天堂久久这里只有精品美国| 亚洲精品中文一区二区三区| 91九色国产老熟女乱子| 香蕉视频欧美久久精品| 国产精品一级在线播放| 国产黄色片子在线观看| 偷拍丝袜美腿在线观看| 一区二区三区日韩电影在线| 97色伦97色伦国产在线| 青青久久精品国产亚洲av| av成人资源一区久久| 亚洲一区二区中文字幕av| 丁香六月色婷婷亚洲激情| 快播av手机在线播放| 91大片在线观看视频| 人人狠狠综合久久亚洲| 伊人婷婷综合激情网| 亚洲一区日韩精品电影| 国产精品自产拍av在线| 中文字幕在线精品乱码麻豆| 成年人性生活网站视频| 天堂在线手机av观看| 在线观看免费完整观看一区二区| 日本一本一道高清不卡视频 | 国产精品果亚洲av无人区一区| 精品少妇高潮蜜臀av| 变态另类专区一区二区三区| 欧美日韩亚洲国产极品| 国产精品成久久久久三级| av电影网站中文字幕| 亚洲综合日韩精品在线|