成人免费xxxxx在线视频软件_久久精品久久久_亚洲国产精品久久久_天天色天天色_亚洲人成一区_欧美一级欧美三级在线观看

Java架構(gòu)之路(多線程)JMM和volatile關(guān)鍵字

開發(fā) 架構(gòu)
我們今天講的JMM和JVM虛擬機(jī)沒有半毛錢關(guān)系,千萬不要把JMM的任何事情聯(lián)想到JVM,把JMM當(dāng)做一個(gè)完全新的事物去理解和認(rèn)識。

說到JMM大家一定很陌生,被我們所熟知的一定是jvm虛擬機(jī),而我們今天講的JMM和JVM虛擬機(jī)沒有半毛錢關(guān)系,千萬不要把JMM的任何事情聯(lián)想到JVM,把JMM當(dāng)做一個(gè)完全新的事物去理解和認(rèn)識。

我們先看一下計(jì)算機(jī)的理論模型,也是馮諾依曼計(jì)算機(jī)模型,先來張圖。

其實(shí)我們更關(guān)注與計(jì)算機(jī)的內(nèi)部CPU的計(jì)算和內(nèi)存之間的關(guān)系。我們在來深入的看一下是如何計(jì)算的。

我們來看一下這個(gè)玩意的處理流程啊,當(dāng)我們的數(shù)據(jù)和方法加載的內(nèi)存區(qū),需要處理時(shí),內(nèi)存將數(shù)據(jù)和方法傳遞到CPU的L3->L2->L1然后再進(jìn)入到CPU進(jìn)行計(jì)算,然后再由L1->L2->L3->再返回到主內(nèi)存中,但是我們的科技反展的很快的,現(xiàn)在貌似沒有單核的CPU了吧,什么8核16核的CPU隨處可見,我們這里只的CPU只是CPU的一個(gè)核來計(jì)算這些玩意。假設(shè)我們的方法是f(x) = x + 1,我們?nèi)雲(yún)⑹?,期望得到結(jié)果是2,1+1=2,我計(jì)算的沒錯(cuò)吧。如果我們兩個(gè)核同時(shí)執(zhí)行該方法呢?我們的CPU2反應(yīng)稍微慢了一點(diǎn)呢?假如當(dāng)我們的內(nèi)存再向CPU2發(fā)送參數(shù)時(shí),可能CPU1已經(jīng)計(jì)算完成并且已經(jīng)返回了。這時(shí)CPU2取得的參數(shù)就是2,這時(shí)再進(jìn)行計(jì)算就是2+1了。結(jié)果并不是我們期望的結(jié)果。這其實(shí)就是數(shù)據(jù)未同步造成的。我們應(yīng)該想盡我們的辦法去同步一下數(shù)據(jù)。

 

我們中間加了一層緩存一致性協(xié)議。也就是我們的MESI,在多處理器系統(tǒng)中,每個(gè)處理器都有自己的高速緩存,而它們的又共享同一個(gè)主內(nèi)存

我來簡單說一下,我們的MESI是咋回事,是怎么做到緩存一致性的。英語不好,我就不誤解大家解釋MESI是什么單詞的縮寫了(是不是縮寫我也不知道,但是我知道工作原理)。

我們還是從內(nèi)存到CPU的這條線路,這時(shí)我們多了一個(gè)MESI,當(dāng)變量X被共同讀取時(shí),CPU1和CPU2是共享一個(gè)X變量,但是分別存在CPU1和2內(nèi),也就是我們X(S)的狀態(tài)。然后CPU1和2一起準(zhǔn)備要計(jì)算了。

然后1和2一定會(huì)有一個(gè)厲害的。比如1得到了勝利,這時(shí)CPU1里的X(S)變?yōu)閄(E)由共享狀態(tài)變?yōu)楠?dú)享狀態(tài),并且告訴CPU2把X(S)變?yōu)閄(I)的狀態(tài),由共享狀態(tài)變?yōu)槭顟B(tài)。然后CPU1就可以計(jì)算了。計(jì)算完成,

又將X(S)變?yōu)閄(M)的狀態(tài),由獨(dú)享狀態(tài)變?yōu)榱诵薷牡臓顟B(tài)。

M: 被修改(Modified)

該緩存行只被緩存在該CPU的緩存中,并且是被修改過的(dirty),即與主存中的數(shù)據(jù)不一致,該緩存行中的內(nèi)存需要在未來的某個(gè)時(shí)間點(diǎn)(允許其它CPU讀取請主存中相應(yīng)內(nèi)存之前)寫回(write back)主存。

當(dāng)被寫回主存之后,該緩存行的狀態(tài)會(huì)變成獨(dú)享(exclusive)狀態(tài)。

E: 獨(dú)享的(Exclusive)

該緩存行只被緩存在該CPU的緩存中,它是未被修改過的(clean),與主存中數(shù)據(jù)一致。該狀態(tài)可以在任何時(shí)刻當(dāng)有其它CPU讀取該內(nèi)存時(shí)變成共享狀態(tài)(shared)。

同樣地,當(dāng)CPU修改該緩存行中內(nèi)容時(shí),該狀態(tài)可以變成Modified狀態(tài)。

S: 共享的(Shared)

該狀態(tài)意味著該緩存行可能被多個(gè)CPU緩存,并且各個(gè)緩存中的數(shù)據(jù)與主存數(shù)據(jù)一致(clean),當(dāng)有一個(gè)CPU修改該緩存行中,其它CPU中該緩存行可以被作廢(變成無效狀態(tài)(Invalid))。

I: 無效的(Invalid)

說到這也就是是我們JMM的內(nèi)存模型的工作機(jī)制了。所以說JMM是一個(gè)虛擬的,和JVM一點(diǎn)關(guān)系都沒有的。切記不要混淆。

這里也有三個(gè)重要的知識點(diǎn)。

JVM 內(nèi)存模型(JMM) 三大特性

原子性:指一個(gè)操作是不可中斷的,即使是多個(gè)線程一起執(zhí)行的時(shí)候,一個(gè)操作一旦開始,就不會(huì)被其他線程干擾

比如,對于一個(gè)靜態(tài)全局變量int i,兩個(gè)線程同時(shí)對它賦值,線程A 給他賦值 1,線程 B 給它賦值為 -1,。那么不管這兩個(gè)線程以何種方式,何種步調(diào)工作,i的值要么是1,要么是-1,線程A和線程B之間是沒有干擾的。這就是原子性的一個(gè)特點(diǎn),不可被中斷

可見性:指當(dāng)一個(gè)線程修改了某一個(gè)共享變量的值,其他線程是否能夠立即知道這個(gè)修改。顯然,對于串行程序來說,可見性問題  是不存在。因?yàn)槟阍谌魏我粋€(gè)操作步驟中修改某個(gè)變量,那么在后續(xù)的步驟中,讀取這個(gè)變量的值,一定是修改后的新值。但是這個(gè)問題在并行程序中就不見得了。如果一個(gè)線程修改了某一個(gè)全局變量,那么其他線程未必可以馬上知道這個(gè)改動(dòng)。

有序性:對于一個(gè)線程的執(zhí)行代碼而言,我們總是習(xí)慣地認(rèn)為代碼的執(zhí)行時(shí)從先往后,依次執(zhí)行的。這樣的理解也不能說完全錯(cuò)誤,因?yàn)榫鸵粋€(gè)線程而言,確實(shí)會(huì)這樣。但是在并發(fā)時(shí),程序的執(zhí)行可能就會(huì)出現(xiàn)亂序。給人直觀的感覺就是:寫在前面的代碼,會(huì)在后面執(zhí)行。有序性問題的原因是因?yàn)槌绦蛟趫?zhí)行時(shí),可能會(huì)進(jìn)行指令重排,重排后的指令與原指令的順序未必一致(指令重排后面會(huì)說)。

我們來看一下volatile關(guān)鍵字

先看一段代碼吧,不上代碼,總覺得是自己沒練習(xí)到位。 

  1. private static  int counter = 0 
  2.     public static void main(String[] args) {  
  3.         for (int i = 0; i < 10; i++) {  
  4.             Thread thread = new Thread(()-> 
  5.                 for (int j = 0; j < 1000; j++) {  
  6.                     counter++; //不是一個(gè)原子操作,第一輪循環(huán)結(jié)果是沒有刷入主存,這一輪循環(huán)已經(jīng)無效  
  7.                 }  
  8.             });  
  9.             thread.start();  
  10.         }  
  11.         try {  
  12.             Thread.sleep(1000);  
  13.         } catch (InterruptedException e) {  
  14.             e.printStackTrace();  
  15.         }  
  16.         System.out.println(counter);  
  17.     } 

按照J(rèn)MM的思想流程來解讀一下這段代碼,我們先創(chuàng)建10個(gè)線程。我們這里叫做T1,T2,T3...T100。然后分別去拿counter這個(gè)數(shù)字,然后疊加1,循環(huán)1000-counter次。當(dāng)T1拿到counter,開始計(jì)算,假如,我們計(jì)算到第50次時(shí),這時(shí)線程T2,也開始要拿counter這個(gè)數(shù)字,這時(shí)得到的counter數(shù)字為50,則T2就要循環(huán)950次,最后我們計(jì)算得到的counter就是9950。也就是說,內(nèi)部是沒有內(nèi)存一致性協(xié)議的。所以我們的輸出一定是<=10000的數(shù)字。

我們來嘗試改一下代碼,使用一下我們的volatile關(guān)鍵字。 

  1. private static volatile int counter = 0 
  2.     public static void main(String[] args) {  
  3.         for (int i = 0; i < 10; i++) {  
  4.             Thread thread = new Thread(()-> 
  5.                 for (int j = 0; j < 1000; j++) {  
  6.                     counter++; //不是一個(gè)原子操作,第一輪循環(huán)結(jié)果是沒有刷入主存,這一輪循環(huán)已經(jīng)無效  
  7.                 }  
  8.             });  
  9.             thread.start();  
  10.         }  
  11.         try {  
  12.             Thread.sleep(1000);  
  13.         } catch (InterruptedException e) {  
  14.             e.printStackTrace();  
  15.         }  
  16.         System.out.println(counter);  
  17.     } 

這時(shí)我們加入了volatile關(guān)鍵字,我們經(jīng)過多次運(yùn)行會(huì)發(fā)現(xiàn),每次結(jié)果都為10000,也就是說每次都是我們期待的結(jié)果,volatile可以保證線程可見性且提供了一定的有序性,但是無法保證原子性。在JVM底層volatile是采用“內(nèi)存屏障”來實(shí)現(xiàn)的。

也就是我們加入了volatile關(guān)鍵字時(shí),java代碼運(yùn)行過程中,會(huì)強(qiáng)制給予一層內(nèi)存一致性的屏障,做到了,我們計(jì)算直接不會(huì)相互影響,得到我們預(yù)期的結(jié)果。

1、可見性實(shí)現(xiàn):

在前文中已經(jīng)提及過,線程本身并不直接與主內(nèi)存進(jìn)行數(shù)據(jù)的交互,而是通過線程的工作內(nèi)存來完成相應(yīng)的操作。這也是導(dǎo)致線程間數(shù)據(jù)不可見的本質(zhì)原因。因此要實(shí)現(xiàn)volatile變量的可見性,直接從這方面入手即可。對volatile變量的寫操作與普通變量的主要區(qū)別有兩點(diǎn):

 (1)修改volatile變量時(shí)會(huì)強(qiáng)制將修改后的值刷新的主內(nèi)存中。

 (2)修改volatile變量后會(huì)導(dǎo)致其他線程工作內(nèi)存中對應(yīng)的變量值失效。因此,再讀取該變量值的時(shí)候就需要重新從讀取主內(nèi)存中的值。相當(dāng)于上文說到的從S->E,另一個(gè)線程從S->I的過程。

  通過這兩個(gè)操作,就可以解決volatile變量的可見性問題。

2、內(nèi)存屏障

為了實(shí)現(xiàn)volatile可見性和happen-befor的語義。JVM底層是通過一個(gè)叫做“內(nèi)存屏障”的東西來完成。內(nèi)存屏障,也叫做內(nèi)存柵欄,是一組處理器指令,用于實(shí)現(xiàn)對內(nèi)存操作的順序限制。下面是完成上述規(guī)則所要求的內(nèi)存屏障:

Required barriers 2nd operation
1st operation Normal Load Normal Store Volatile Load Volatile Store
Normal Load       LoadStore
Normal Store       StoreStore
Volatile Load LoadLoad LoadStore LoadLoad LoadStore
Volatile Store     StoreLoad StoreStore
 
(1)LoadLoad 屏障

執(zhí)行順序:Load1—>Loadload—>Load2

確保Load2及后續(xù)Load指令加載數(shù)據(jù)之前能訪問到Load1加載的數(shù)據(jù)。

(2)StoreStore 屏障

執(zhí)行順序:Store1—>StoreStore—>Store2

確保Store2以及后續(xù)Store指令執(zhí)行前,Store1操作的數(shù)據(jù)對其它處理器可見。

(3)LoadStore 屏障

執(zhí)行順序:Load1—>LoadStore—>Store2

確保Store2和后續(xù)Store指令執(zhí)行前,可以訪問到Load1加載的數(shù)據(jù)。

(4)StoreLoad 屏障

執(zhí)行順序: Store1—> StoreLoad—>Load2

確保Load2和后續(xù)的Load指令讀取之前,Store1的數(shù)據(jù)對其他處理器是可見的。

 總體上來說volatile的理解還是比較困難的,如果不是特別理解,也不用急,完全理解需要一個(gè)過程,在后續(xù)的文章中也還會(huì)多次看到volatile的使用場景。這里暫且對volatile的基礎(chǔ)知識和原來有一個(gè)基本的了解。總體來說,volatile是并發(fā)編程中的一種優(yōu)化,在某些場景下可以代替Synchronized。但是,volatile的不能完全取代Synchronized的位置,只有在一些特殊的場景下,才能適用volatile。總的來說,必須同時(shí)滿足下面兩個(gè)條件才能保證在并發(fā)環(huán)境的線程安全:

 (1)對變量的寫操作不依賴于當(dāng)前值。

 (2)該變量沒有包含在具有其他變量的不變式中。

 參考地址:https://www.cnblogs.com/paddix/p/5428507.html

JMM-同步八種操作介紹

(1)lock(鎖定):作用于主內(nèi)存的變量,把一個(gè)變量標(biāo)記為一條線程獨(dú)占狀態(tài)

(2)unlock(解鎖):作用于主內(nèi)存的變量,把一個(gè)處于鎖定狀態(tài)的變量釋放出來,釋放后的 變量才可以被其他線程鎖定

(3)read(讀取):作用于主內(nèi)存的變量,把一個(gè)變量值從主內(nèi)存?zhèn)鬏數(shù)骄€程的工作內(nèi)存中, 以便隨后的load動(dòng)作使用

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

(5)use(使用):作用于工作內(nèi)存的變量,把工作內(nèi)存中的一個(gè)變量值傳遞給執(zhí)行引擎

(6)assign(賦值):作用于工作內(nèi)存的變量,它把一個(gè)從執(zhí)行引擎接收到的值賦給工作內(nèi)存 的變量

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

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

流程圖大致是這樣的:

 

 

責(zé)任編輯:龐桂玉 來源: JAVA高級架構(gòu)
相關(guān)推薦

2009-06-29 18:14:23

Java多線程volatile關(guān)鍵字

2020-11-11 08:45:48

Java

2017-05-27 20:59:30

Java多線程synchronize

2025-06-13 08:00:00

Java并發(fā)編程volatile

2022-06-29 08:05:25

Volatile關(guān)鍵字類型

2011-06-14 13:26:27

volatile

2019-09-04 14:14:52

Java編程數(shù)據(jù)

2010-03-15 18:11:38

Java多線程

2024-02-21 20:46:48

C++編程volatile

2011-06-21 09:50:51

volatile

2009-06-29 18:26:11

Java多線程Synchronize同步類

2022-08-17 07:53:10

Volatile關(guān)鍵字原子性

2018-01-19 10:43:06

Java面試官volatile關(guān)鍵字

2023-06-26 08:02:34

JSR重排序volatile

2019-12-20 15:19:41

Synchroinze線程安全

2024-03-15 08:18:25

volatileAtomic關(guān)鍵字

2016-09-19 21:53:30

Java并發(fā)編程解析volatile

2012-03-01 12:50:03

Java

2023-11-28 21:50:39

finalstaticvolatile

2025-01-09 10:30:40

點(diǎn)贊
收藏

51CTO技術(shù)棧公眾號

主站蜘蛛池模板: 欧美精品一区三区 | 免费的黄色片子 | 婷婷成人在线 | 97人人澡人人爽91综合色 | 亚洲视频 欧美视频 | 成人福利网站 | 国产成人精品一区二区三 | 欧美日韩1区 | 欧美日韩专区 | 日本福利片 | 欧美精品在线播放 | 激情六月丁香婷婷 | 欧美日韩国产在线观看 | 国产成人精品一区二区 | 精品久久久久国产免费第一页 | 男女羞羞的网站 | 久久免费精品 | 亚洲一区在线播放 | 天天天天天操 | 国产欧美一区二区精品久导航 | 亚洲国产精品99久久久久久久久 | 中文字幕1区2区 | 国产欧美一区二区三区日本久久久 | 欧美精品福利 | 欧美中文字幕一区 | 久久久久无码国产精品一区 | 高清人人天天夜夜曰狠狠狠狠 | 天堂一区二区三区 | 成人日韩av | a国产视频| 超碰免费在线观看 | 欧美三区 | 亚洲av毛片 | 这里只有精品99re | 九七午夜剧场福利写真 | 久久国产欧美日韩精品 | 中文字幕日韩欧美一区二区三区 | 久久久久久黄 | 亚洲欧美一区二区三区1000 | se婷婷| 欧美激情黄色 |