Java併發編程學習前期知識下篇

Java併發編程學習前期知識下篇

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

Java併發編程學習前期知識下篇

Java併發編程學習前期知識下篇

Java併發編程學習前期知識下篇

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

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

CPU相關知識

先來看看凱哥電腦配置:

Java併發編程學習前期知識下篇


Java併發編程學習前期知識下篇

Java併發編程學習前期知識下篇

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

CPU處理是4核8線程,

緩存有三級。

其中一級數據緩存和指令緩存都是32K,

二級緩存256K,

三級緩存是6M.

電腦的內存是24G

為什麼要說這些呢 ?

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

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

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

主內存:就是凱哥本子上的24G物理內存條

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

線程工作原理,如下圖:

Java併發編程學習前期知識下篇

說明:

主內存中變量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通過嗅探在總線上傳播的數據來實時檢查自己緩存的值是不是已經過期了。如果發下自己緩存中的數據已經被修改了,則就會將當前的處理器中緩存數據狀態設置為無效,當這個處理器需要對這個數據進行操作的似乎和,會重新從主內存中,把最新的數據讀取到自己緩存中。

Volatile兩條實現原理

1:彙編代碼的lock前綴指令會引起處理器緩存寫回到主內存中。

當有lock指令的緩存,在其聲言期間,能搞保證處理器可以獨佔任何共享的內存。同時緩存一致性會阻止同時修改由兩個以上處理器緩存的內存區域數據

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

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

總結:

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


分享到:


相關文章: