最新国产好看的视频,伊人天堂AV在线,国产Aaaaaa视频,蜜臀视频在线观看一区,人妻av色图,密臀久久久精品影片,青青视频免费观看毛片,久草在线观看视,国产三级精品色情在线

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

 更新時(shí)間:2019年05月13日 14:31:42   作者:zYinux  
這篇文章主要介紹了Java并發(fā)中的內(nèi)存模型,小編覺(jué)得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧

什么是JavaMemoryModel(JMM)?

JMM通過(guò)構(gòu)建一個(gè)統(tǒng)一的內(nèi)存模型來(lái)屏蔽掉不同硬件平臺(tái)和不同操作系統(tǒng)之間的差異,讓Java開(kāi)發(fā)者無(wú)需關(guān)注不同平臺(tái)之間的差異,達(dá)到一次編譯,隨處運(yùn)行的目的,這也正是Java的設(shè)計(jì)目的之一。

CPU和內(nèi)存

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

沒(méi)有什么問(wèn)題是一個(gè)緩存不能解決的,如果有,那就再加一個(gè)緩存 ——魯迅:反正我沒(méi)說(shuō)這句話

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

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

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

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

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

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

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

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

  • lock (鎖定)作用于主內(nèi)存的變量,將一個(gè)變量標(biāo)識(shí)為一個(gè)線程獨(dú)占狀態(tài)
  • unlock(解鎖)作用于主內(nèi)存的變量,將一個(gè)處于鎖定狀態(tài)的變量釋放出來(lái),釋放之后才能被其他線程鎖定
  • read(讀?。┳饔糜谥鲀?nèi)存的變量,將一個(gè)變量的值從主內(nèi)存?zhèn)鬏數(shù)骄€程工作內(nèi)存中,便于之后的load操作使用
  • load(載入)作用于工作內(nèi)存的變量,它把read操作從主內(nèi)存中得到的變量值放入工作內(nèi)存的變量副本中。
  • use(使用)作用于工作內(nèi)存的變量,把工作內(nèi)存中的一個(gè)變量值傳遞給執(zhí)行引擎,每當(dāng)虛擬機(jī)遇到一個(gè)需要使用變量的值的字節(jié)碼指令時(shí)將會(huì)執(zhí)行這個(gè)操作。
  • assign(賦值)作用于工作內(nèi)存的變量,它把一個(gè)從執(zhí)行引擎接收到的值賦值給工作內(nèi)存的變量,每當(dāng)虛擬機(jī)遇到一個(gè)給變量賦值的字節(jié)碼指令時(shí)執(zhí)行這個(gè)操作。
  • store(存儲(chǔ))作用于工作內(nèi)存的變量,把工作內(nèi)存中的一個(gè)變量的值傳送到主內(nèi)存中,以便隨后的write的操作。
  • write(寫(xiě)入)作用于主內(nèi)存的變量,它把store操作從工作內(nèi)存中一個(gè)變量的值傳送到主內(nèi)存的變量中。

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

  • 如果要把一個(gè)變量從主內(nèi)存中復(fù)制到工作內(nèi)存,就需要按順尋地執(zhí)行read和load操作, 如果把變量從工作內(nèi)存中同步回主內(nèi)存中,就要按順序地執(zhí)行store和write操作。但Java內(nèi)存模型只要求上述操作必須按順序執(zhí)行,而沒(méi)有保證必須是連續(xù)執(zhí)行。
  • 不允許read和load、store和write操作之一單獨(dú)出現(xiàn)
  • 不允許一個(gè)線程丟棄它的最近assign的操作,即變量在工作內(nèi)存中改變了之后必須同步到主內(nèi)存中。
  • 不允許一個(gè)線程無(wú)原因地(沒(méi)有發(fā)生過(guò)任何assign操作)把數(shù)據(jù)從工作內(nèi)存同步回主內(nèi)存中。
  • 一個(gè)新的變量只能在主內(nèi)存中誕生,不允許在工作內(nèi)存中直接使用一個(gè)未被初始化(load或assign)的變量。即就是對(duì)一個(gè)變量實(shí)施use和store操作之前,必須先執(zhí)行過(guò)了assign和load操作。
  • 一個(gè)變量在同一時(shí)刻只允許一條線程對(duì)其進(jìn)行l(wèi)ock操作,但lock操作可以被同一條線程重復(fù)執(zhí)行多次,多次執(zhí)行l(wèi)ock后,只有執(zhí)行相同次數(shù)的unlock操作,變量才會(huì)被解鎖。lock和unlock必須成對(duì)出現(xiàn)
  • 如果對(duì)一個(gè)變量執(zhí)行l(wèi)ock操作,將會(huì)清空工作內(nèi)存中此變量的值,在執(zhí)行引擎使用這個(gè)變量前需要重新執(zhí)行l(wèi)oad或assign操作初始化變量的值
  • 如果一個(gè)變量事先沒(méi)有被lock操作鎖定,則不允許對(duì)它執(zhí)行unlock操作;也不允許去unlock一個(gè)被其他線程鎖定的變量。
  • 對(duì)一個(gè)變量執(zhí)行unlock操作之前,必須先把此變量同步到主內(nèi)存中(執(zhí)行store和write操作)。

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

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

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

內(nèi)存可見(jiàn)性

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

指令重排序

指令重排序是一個(gè)大部分CPU都有的操作,同時(shí)JVM在運(yùn)行時(shí)也會(huì)存在指令重排序的操作。 簡(jiǎn)單舉個(gè):chestnut:

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

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

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

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

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

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

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

long,double的問(wèn)題

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

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

性能問(wèn)題

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

synchronized的內(nèi)存語(yǔ)義

簡(jiǎn)單概括就是

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

final的內(nèi)存語(yǔ)義

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

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

后記之CPU緩存行和偽共享

什么是偽共享

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

如何避免偽共享

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

以上就是本文的全部?jī)?nèi)容,希望對(duì)大家的學(xué)習(xí)有所幫助,也希望大家多多支持腳本之家。

相關(guān)文章

  • RabbitMQ實(shí)現(xiàn)延時(shí)消息的兩種方法實(shí)戰(zhàn)教程

    RabbitMQ實(shí)現(xiàn)延時(shí)消息的兩種方法實(shí)戰(zhàn)教程

    這篇文章主要介紹了RabbitMQ實(shí)現(xiàn)延時(shí)消息的兩種方法,本文給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下
    2023-09-09
  • Java利用redis實(shí)現(xiàn)防止接口重復(fù)提交

    Java利用redis實(shí)現(xiàn)防止接口重復(fù)提交

    本文主要為大家詳細(xì)介紹了Java如何利用redis實(shí)現(xiàn)防止接口重復(fù)提交,文中通過(guò)示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下
    2022-11-11
  • mybatis空值插入處理的解決方法

    mybatis空值插入處理的解決方法

    本文主要介紹了mybatis空值插入處理的解決方法,文中通過(guò)示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下
    2021-09-09
  • Java中的動(dòng)態(tài)代理原理及實(shí)現(xiàn)

    Java中的動(dòng)態(tài)代理原理及實(shí)現(xiàn)

    這篇文章主要介紹了Java中的動(dòng)態(tài)代理原理及實(shí)現(xiàn),動(dòng)態(tài)是相對(duì)于靜態(tài)而言,何為靜態(tài),即編碼時(shí)手動(dòng)編寫(xiě)代理類(lèi)、委托類(lèi),而動(dòng)態(tài)呢,是不編寫(xiě)具體實(shí)現(xiàn)類(lèi),等到使用時(shí),動(dòng)態(tài)創(chuàng)建一個(gè)來(lái)實(shí)現(xiàn)代理的目的,需要的朋友可以參考下
    2023-12-12
  • Windows系統(tǒng)中Java調(diào)用cmd命令及執(zhí)行exe程序的方法

    Windows系統(tǒng)中Java調(diào)用cmd命令及執(zhí)行exe程序的方法

    這篇文章主要介紹了Windows系統(tǒng)中Java調(diào)用cmd命令及執(zhí)行exe程序的方法,主要用到了IOException類(lèi),需要的朋友可以參考下
    2016-03-03
  • Java中如何判斷線程池任務(wù)已執(zhí)行完成

    Java中如何判斷線程池任務(wù)已執(zhí)行完成

    這篇文章主要介紹了Java中如何判斷線程池任務(wù)已執(zhí)行完成,文章圍繞主題展開(kāi)詳細(xì)相關(guān)資料,具有一定的參考價(jià)值需要的小伙伴可以參考以參考一下
    2022-05-05
  • Spring security如何重寫(xiě)Filter實(shí)現(xiàn)json登錄

    Spring security如何重寫(xiě)Filter實(shí)現(xiàn)json登錄

    這篇文章主要介紹了Spring security 如何重寫(xiě)Filter實(shí)現(xiàn)json登錄,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教
    2021-09-09
  • jdk21環(huán)境配置詳細(xì)步驟記錄

    jdk21環(huán)境配置詳細(xì)步驟記錄

    JDK是整個(gè)Java開(kāi)發(fā)的核心,它包含了Java的運(yùn)行環(huán)境和Java工具,這篇文章主要給大家介紹了關(guān)于jdk21環(huán)境配置的相關(guān)資料,文中通過(guò)圖文介紹的非常詳細(xì),需要的朋友可以參考下
    2023-11-11
  • 一文了解mybatis的延遲加載

    一文了解mybatis的延遲加載

    本文主要為大家詳細(xì)介紹下mybatis的延遲加載,從原理上介紹下怎么使用、有什么好處能規(guī)避什么問(wèn)題。感興趣的小伙伴可以跟隨小編一起學(xué)習(xí)一下
    2022-07-07
  • java實(shí)現(xiàn)圖片縮放、旋轉(zhuǎn)和馬賽克化

    java實(shí)現(xiàn)圖片縮放、旋轉(zhuǎn)和馬賽克化

    這篇文章主要為大家詳細(xì)介紹了java實(shí)現(xiàn)圖片縮放、旋轉(zhuǎn)和馬賽克化,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下
    2018-03-03

最新評(píng)論

外汇| 康保县| 利川市| 宁海县| 衡水市| 布尔津县| 临湘市| 柏乡县| 新邵县| 泰来县| 延长县| 阿拉善盟| 琼结县| 海晏县| 平和县| 绍兴市| 苏尼特左旗| 凯里市| 厦门市| 隆德县| 房山区| 婺源县| 平凉市| 渭南市| 大足县| 勃利县| 出国| 广饶县| 天津市| 招远市| 宜春市| 万年县| 定结县| 务川| 西贡区| 来宾市| 土默特右旗| 汽车| 长海县| 东台市| 林周县|