您好,登錄后才能下訂單哦!
本篇內容介紹了“如何理解JMM內存模型”的有關知識,在實際案例的操作過程中,不少人都會遇到這樣的困境,接下來就讓小編帶領大家學習一下如何處理這些情況吧!希望大家仔細閱讀,能夠學有所成!
CPU在執行的時候,肯定要有數據,而數據在內存中放著呢,這里的內存就是計算機的物理內存,剛開始還好,但是隨著技術的發展,CPU處理的速度越來越快,而從內存中讀取和寫入數據的過程和CPU的執行速度比起來差距就會越來越大,所說設計師,就在物理內存與CPU之間,加入了緩存的概念:也就是CPU在運行的時候,會將運算需要的數據從主存復制一份到CPU的高速緩存當中,那么CPU進行計算時就可以直接從它的高速緩存讀取數據和向其中寫入數據,當運算結束之后,再將高速緩存中的數據刷新到主存當中。
(說到這里,你應該能想到在高并發,使用多線程處理的時候,存在的問題。)
單核CPU只含有一套L1,L2,L3緩存;如果CPU含有多個核心,即多核CPU,則每個核心都含有一套L1(甚至和L2)緩存,而共享L3(或者和L2)緩存。
當你的計算式是:
單核CPU,單線程。 核心的緩存只被一個線程訪問。緩存獨占,不會出現訪問沖突等問題。
單核CPU,多線程。 進程中的多個線程會同時訪問進程中的共享數據,CPU將某塊內存加載到緩存后,不同線程在訪問相同的物理地址的時候,都會映射到相同的緩存位置,這樣即使發生線程的切換,緩存仍然不會失效。但由于任何時刻只能有一個線程在執行,因此不會出現緩存訪問沖突。
多核CPU,多線程。 每個核都至少有一個L1 緩存。多個線程訪問進程中的某個共享內存,且這多個線程分別在不同的核心上執行,則每個核心都會在各自的caehe中保留一份共享內存的緩沖。由于多核是可以并行的,可能會出現多個線程同時寫各自的緩存的情況,而各自的cache之間的數據就有可能不同。
JMM全稱為Java Memory Model java內存模型,它只是一組規范,并不真實存在,(看好了,只是一組規范、一個定義),它描述的是一個規則。通過這組規范定義程序中的變量的訪問方式。以此來解決多核CPU多線程時造成的問題(請想一想為什么會是多核CPU多線程時)。
JMM規定了工作內存、主內存,而主內存是共享區域,所有線程都可以訪問,工作內存是每個線程的工作內存,每個線程對變量的操作必須在自己的工作內存中進行。在運行時將變量從主內存中拷貝到工作內存中,對變量進行操作,操作完后再將變量寫會主內存,不能直接在主內存中操作變量。再說一遍:這是規范,是java定義的一組程序運行時的規范。看到這,是不是發現與計算機的內存模型特別像
在這里再說一下java的內存區域:堆、棧、方法區、本地方法區、程序計數器
方法區:線程共享區域,主要用于存儲虛擬機加載的類信息、常量、靜態變量等數據。
堆:線程共享區域,虛擬機啟動時創建,主要用于存放對象的實例,所以也是java垃圾回收最頻繁的一個區域
棧:線程私有區域,與線程同時創建,棧數量與線程數量相等,以棧幀定義,執行每個方法時,都會創建一個棧幀存儲方法的信息:操作數棧、動態鏈接方法、返回值、返回地址等信息,每個方法執行從調用到結束,對應著這個棧幀的入棧出棧。
程序計數器、本地方法棧我們先不關心,有心者請自行學習。
如果你看到這,我覺得你應該會疑惑,說JMM的時候,會什么要把計算機的內存模型、JAVA的內存區域都描述一下,稍后你就會知道。
在這里我還要再強調一遍,jmm內存模型的主內存和工作內存與java內存區域的堆、棧、方法區等不是同一個層次的,無法類比。一個是規范、規則,就相當于校規似的。如果真要對應,那就如同你想的 棧---工作內存,堆、方法區---主內存。
現在我要將這些聯系起來了:
首先jmm是一組規范,是java提出來的規范,那么java在設計的時候,肯定也符合這組規范。我認為java的內存區域就是根據jmm規范設計的,所以上面說了,他們不屬于一個層次,無法類比,只能說是java內存區域,符合jmm的規范。
然后我們也知道,線程是cpu調度的最小單元,也就是說cpu的內核執行線程,上面也說了,執行方法,也就是一個棧幀入棧出棧的過程,那么cpu內核執行線程,就是操作的棧中的數據,那么就是cpu內核把棧中的數據拷貝到它的緩存中去運行。可能有點模糊,我認為你就記住,cpu執行時的數據就是棧中的數據。
你可能在想,那堆中的數據時怎么操作的?我認為是這樣:看到這,我也認為電腦面前的你知道了堆在棧中存的是一個地址,那么cpu內核在運行時,不也是根據這個地址找到那個數據了嘛,對cpu來講,你就是一份數據,在它面前你跟棧中的數據都是一樣的,都是數據,然后加到它的緩存中。
如果你讀的有點蒙,那就請再讀幾遍,書讀百遍,其義自見:也就是說你在讀第一遍的時候,你根本就沒理解,你的腦子里只有讀過的字,并沒有理解到寫的含義,打個比方說:我說筷子,你腦海的潛意識中是筷子兩根棍的樣子,而不是“筷子”這兩個字。
然后,請回顧上面提到的兩個問題,我也在這貼出來一段代碼,以此來表示多核多線程產生的問題:
public class VolatileFaceThread{ boolean isRunning = true; void m() { System.out.println("isRunning start"); while(isRunning) { } System.out.println("isRunning end"); } public static void main(String\[\] args) { VolatileFaceThread vft = new VolatileFaceThread(); new Thread(vft :: m).start(); try { TimeUnit.SECONDS.sleep(1); } catch (Exception e) { // TODO Auto-generated catch block e.printStackTrace(); } vft.isRunning = false; System.out.println("update isRunning..."); } }
預期效果:新啟線程會一直循環下去
這段代碼,新啟的線程將會一直循環下去,不會被停止。試驗此段代碼時,如果有的實際效果是在主線程修改后,新啟的線程也跟著停止了,那么你的電腦可能1核在運行。(當初我在這被卡了好幾天,讓同事運行時,它運行的實際效果就是預期效果)。
這就是因為,兩個線程被兩個內核運行,他們把值讀取到自己的緩存中運行。而緩存是每個內核私有的,主線程修改了值,對新啟線程來說是不可見的,故新啟線程會一直循環。
那怎么解決呢,就是讓線程可見唄,java中有這一個關鍵字:volatile-----內存可見性、禁止指令重排序。
被volatile關鍵字修飾的變量對所有線程總是可見的,也就是在一個線程修改了一個被volatile關鍵字修飾變量的值,新值總是可以被其他線程立即得知。
“如何理解JMM內存模型”的內容就介紹到這里了,感謝大家的閱讀。如果想了解更多行業相關的知識可以關注億速云網站,小編將為大家輸出更多高質量的實用文章!
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。