天天看點

Java并發程式設計學習前期知識下篇CPU相關知識線程之間可見性深度了解Volatitle是如何保證可見性的呢?Volatile兩條實作原理

Java并發程式設計學習前期知識下篇

通過上一篇《Java并發程式設計學習前期知識上篇》我們知道了在Java并發中的可見性是什麼?volatile的定義以及JMM的定義。我們先來看看幾個大廠真實的面試題:

Java并發程式設計學習前期知識下篇CPU相關知識線程之間可見性深度了解Volatitle是如何保證可見性的呢?Volatile兩條實作原理

編輯

Java并發程式設計學習前期知識下篇CPU相關知識線程之間可見性深度了解Volatitle是如何保證可見性的呢?Volatile兩條實作原理

編輯

Java并發程式設計學習前期知識下篇CPU相關知識線程之間可見性深度了解Volatitle是如何保證可見性的呢?Volatile兩條實作原理

編輯

從上面幾個真實的面試問題來看,我們可以看到大廠的面試都會問到并發相關的問題。是以

Java并發,這個無論是面試還是在工作中,并發都是會遇到的。Java并發包JUC(java.util.concurrent)有了解過哪些?并發包實作最重要的是什麼?其原理是什麼知道嗎?何為JMM的可見性?volatiile關鍵字是怎麼實作變量可見性的?如果想要學好并發,弄懂了解透徹的話,凱哥覺得以下計算機的知識還是要了解了解。本次《Java并發程式設計-前期準備知識》凱哥準備用兩篇來介紹,主要包括以下内容:簡單介紹記憶體之間可見性是什麼?volatile關鍵字在Java語言規範中是怎麼定義的?知道JVM但是你知道JMM是什麼嗎?計算機中CPU是怎麼處理資料的?通過CPU處理資料來深刻了解線程之間可見性。還有就是volatile是怎麼保證可見性的呢?其實作的兩條原理是什麼?

CPU相關知識

先來看看凱哥電腦組態:

Java并發程式設計學習前期知識下篇CPU相關知識線程之間可見性深度了解Volatitle是如何保證可見性的呢?Volatile兩條實作原理

編輯

Java并發程式設計學習前期知識下篇CPU相關知識線程之間可見性深度了解Volatitle是如何保證可見性的呢?Volatile兩條實作原理

編輯

Java并發程式設計學習前期知識下篇CPU相關知識線程之間可見性深度了解Volatitle是如何保證可見性的呢?Volatile兩條實作原理

編輯

從上圖,可以看到凱哥電腦

CPU處理是4核8線程,

緩存有三級。

其中一級資料緩存和指令緩存都是32K,

二級緩存256K,

三級緩存是6M.

電腦的記憶體是24G

為什麼要說這些呢 ?

因為JVM運作程式的實體其實就是線程,而每個線程在建立的時候JVM都會給其建立一個工作記憶體(有些地方稱之為:棧空間)。工作記憶體是每個線程自己的私有資料區域。Java記憶體模型中規定所有的變量都是存儲在主記憶體中(也就是凱哥24G内記憶體中),主記憶體是共享記憶體區域,所有的線程都可以通路的(也就是說主記憶體中的資料,任意線程都可以通路)。但是線程對變量的操作,如讀取,修改指派操作是在從中記憶體中進行的。是以,一個線程要想操作一個變量,首先是要講變量從主記憶體copy到自己的工作記憶體空間,然後再對自己工作空間中對變量操作,操作完成之後再将變量寫回到主記憶體中去。線程是不能夠直接操作主記憶體中的變量的。各個線程中的工作記憶體存儲的其實就是主記憶體的一個變量副本拷貝。是以不同線程之間是無法通路到對方的工作記憶體的。線程間的通訊(值轉遞)必須通過主記憶體來完成的。

上面這麼大一段話,可以簡單對應凱哥電腦組態:

線程:其實就是凱哥CPU的4核8線程中的線程

主記憶體:就是凱哥本子上的24G實體記憶體條

線程工作記憶體空間:就是緩存(一二三級緩存區域)

線程工作原理,如下圖:

Java并發程式設計學習前期知識下篇CPU相關知識線程之間可見性深度了解Volatitle是如何保證可見性的呢?Volatile兩條實作原理

編輯

說明:

主記憶體中變量int i= 0;

cpu1中的線程1擷取到i變量的時候,會将i從主記憶體中copy一份到自己的工作區域,也就是cpu1 cache中,然後更新i的值為10;

cpu2中的線程2同樣擷取到i變量,從主記憶體中copy一份之後,在自己的工作區cpu2 cache中将i修改成了15;這種情況下就是多核多線程問題。

線程之間可見性深度了解

在這種情況下主記憶體中的i就是兩個線程之間的共享變量了。那麼怎麼能確定cpu1的線程1修改i的值之後,通知cpu2中的線程2的工作區緩存無效呢?這個操作就是線程之間的可見性。

再舉個現實生活中常用的例子:

比如,凱哥現在在和大家分享。今天我釋出之後,你們大家在自己手機或者是PC網友上都能看到凱哥分享的知識點。這個時候有個網友A在看到凱哥分享的東西,感覺有點不好或者是舉個其他的例子或者更容易了解。于是他把凱哥這個文章進行了修改。然後給凱哥留Y。告訴凱哥,凱哥看後,覺得很不錯。等明天,凱哥發文章通知大家,如果用xxx的案例就跟容易讓大家了解了。于是,你們大家知道,哦原來昨天的案例不是最新的了。放棄昨天的,看看今天最新的案例。

如果上面案例看着是多線程那麼可以這麼分析:

主記憶體:凱哥

共享變量:凱哥分享的知識點

多個線程:各位看凱哥分享的網友

其中網友A修改了知識點的内容(網友A修改的是自己手機上的(工作區的)知識點)後通知了凱哥,然後凱哥又通知了各位。各位知道原來自己手裡的不是最新的了,然後放棄重新擷取最新的。

這樣來了解的話,就更容易了解線程的可見性

Volatitle是如何保證可見性的呢?

可以通過JIT編譯器生成的彙編指令來檢視對volatile進行寫操作時候,CPU都做了哪些事情?

如下代碼:

Volatile Singleton instance = new Singleton();

Instance是被volatitle修飾的。

在使用JIT編譯器生成的彙編指令後,有一個重要的代碼:

0x01a2de1d:xxxx:lock addl $0X0,(%esp);

我們可以看到,當一個共享變量被volatile修飾之後,在進行寫操作的時候,會多出一些彙編代碼Lock.在IA-32架構軟體開發手冊中,Lock字首的指令當在多核處理器的時候會引發出兩件事情:

1:将目前的處理器緩存行的資料寫回的主記憶體中(也就是系統的實體緩存中);

2:同時這個寫回記憶體的操作也會使其他CUP裡緩存了記憶體位址的資料被置為無效。

Cpu處理資料方法:

為了提高處理資料,CPU不會直接從記憶體中擷取資料操作的。

這裡我們需要電腦處理資料的速度排序:磁盤(硬碟)<記憶體<高速緩存<CPU。我們可以看出CPU的操作速度比記憶體快很多。是以,如果CPU直接操作記憶體,不僅會影響處理速度還有可能讓記憶體使用壽命變短。這個時候,高速緩存就解決這個問題的。

是以,CPU在處理資料的時候會像将記憶體中的資料到期到進階緩存中(就是一二三級緩存),然後再緩存中進行操作的。

在多核處理器的時候,為了保證各個處理器之間緩存變量是一緻的,就需要實作緩存一緻性協定。其操作就是:各個CPU通過嗅探在總線上傳播的資料來實時檢查自己緩存的值是不是已經過期了。如果發下自己緩存中的資料已經被修改了,則就會将目前的處理器中緩存資料狀态設定為無效,當這個處理器需要對這個資料進行操作的似乎和,會重新從主記憶體中,把最新的資料讀取到自己緩存中。

Volatile兩條實作原理

1:彙編代碼的lock字首指令會引起處理器緩存寫回到主記憶體中。

當有lock指令的緩存,在其聲言期間,能搞保證處理器可以獨占任何共享的記憶體。同時緩存一緻性會阻止同時修改由兩個以上處理器緩存的記憶體區域資料

2:當一個處理器的緩存寫回到主記憶體中之後,會導緻其他處理器的緩存無效

這個是處理器見的控制協定來維護内部緩存的

總結:

通過這兩篇《Java并發程式設計前期準備知識》的了解,我們知道JMM,線程之間共享資料等知識,這樣再接下來學習Java并發程式設計就會簡單一些了。接下來歡迎進入Java并發程式設計學習中!