Java 執行緒記憶體模型

bok發表於2018-08-07

Java 執行緒記憶體模型

連結一

所有執行緒共享主記憶體,每個執行緒有自己的工作記憶體

產生執行緒安全的原因:執行緒的working memory是cpu的暫存器和快取記憶體的抽象描述:現在的計算機,cpu在計算的時候,並不總是從記憶體讀取資料,它的資料讀取順序優先順序 是:暫存器-快取記憶體-記憶體。執行緒耗費的是CPU,執行緒計算的時候,原始的資料來自記憶體,在計算過程中,有些資料可能被頻繁讀取,這些資料被儲存在暫存器和快取記憶體中,當執行緒計算完後,這些快取的資料在適當的時候應該寫回記憶體。當多個執行緒同時讀寫某個記憶體資料時,就會產生多執行緒併發問題,涉及到三個特 性:原子性,有序性,可見性。 支援多執行緒的平臺都會面臨 這種問題,執行在多執行緒平臺上支援多執行緒的語言應該提供解決該問題的方案。

JVM是一個虛擬的計算機,它也會面臨多執行緒併發問題,java程式執行在java虛擬機器平臺上,java程式設計師不可能直接去控制底層執行緒對暫存器快取記憶體記憶體之間的同步,那麼java從語法層面,應該給開發人員提供一種解決方案,這個方案就是諸如 synchronized, volatile,鎖機制(如同步塊,就緒隊 列,阻塞佇列)等等。這些方案只是語法層面的,但我們要從本質上去理解它;

每個執行緒都有自己的執行空間(即工作記憶體),執行緒執行的時候用到某變數,首先要將變數從主記憶體拷貝的自己的工作記憶體空間,然後對變數進行操作:讀取,修改,賦值等,這些均在工作記憶體完成,操作完成後再將變數寫回主記憶體;

各個執行緒都從主記憶體中獲取資料,執行緒之間資料是不可見的;打個比方:主記憶體變數A原始值為1,執行緒1從主記憶體取出變數A,修改A的值為2,線上程1未將變數A寫回主記憶體的時候,執行緒2拿到變數A的值仍然為1;

這便引出“可見性”的概念:當一個共享變數在多個執行緒的工作記憶體中都有副本時,如果一個執行緒修改了這個共享變數的副本值,那麼其他執行緒應該能夠看到這個被修改後的值,這就是多執行緒的可見性問題。

普通變數情況:如執行緒A修改了一個普通變數的值,然後向主記憶體進行寫回,另外一條執行緒B線上程A回寫完成了之後再從主記憶體進行讀取操作,新變數的值才會對執行緒B可見;

如何保證執行緒安全:編寫執行緒安全的程式碼,本質上就是管理對狀態(state)的訪問,而且通常都是共享的、可變的狀態。這裡的狀態就是物件的變數(靜態變數和例項變數)

執行緒安全的前提是該變數是否被多個執行緒訪問, 保證物件的執行緒安全性需要使用同步來協調對其可變狀態的訪問;若是做不到這一點,就會導致髒資料和其他不可預期的後果。無論何時,只要有多於一個的執行緒訪問給定的狀態變數,而且其中某個執行緒會寫入該變數,此時必須使用同步來協調執行緒對該變數的訪問。Java中首要的同步機制是synchronized關鍵字,它提供了獨佔鎖。除此之外,術語“同步”還包括volatile變數,顯式鎖和原子變數的使用。

在沒有正確同步的情況下,如果多個執行緒訪問了同一個變數,你的程式就存在隱患。有3種方法修復它:

l 不要跨執行緒共享變數;

l 使狀態變數為不可變的;或者

l 在任何訪問狀態變數的時候使用同步。

**volatile **要求程式對變數的每次修改,都寫回主記憶體,這樣便對其它執行緒課件,解決了可見性的問題,但是不能保證資料的一致性;特別注意:原子操作:根據Java規範,對於基本型別的賦值或者返回值操作,是原子操作。但這裡的基本資料型別不包括long和double, 因為JVM看到的基本儲存單位是32位,而long 和double都要用64位來表示。所以無法在一個時鐘週期內完成

通俗的講一個物件的狀態就是它的資料,儲存在狀態變數中,比如例項域或者靜態域;無論何時,只要多於一個的執行緒訪問給定的狀態變數。而且其中某個執行緒會寫入該變數,此時必須使用同步來協調執行緒對該變數的訪問;

同步鎖:每個JAVA物件都有且只有一個同步鎖,在任何時刻,最多隻允許一個執行緒擁有這把鎖。

當一個執行緒試圖訪問帶有synchronized(this)標記的程式碼塊時,必須獲得 this關鍵字引用的物件的鎖,在以下的兩種情況下,本執行緒有著不同的命運。

1、 假如這個鎖已經被其它的執行緒佔用,JVM就會把這個執行緒放到本物件的鎖池中。本執行緒進入阻塞狀態。鎖池中可能有很多的執行緒,等到其他的執行緒釋放了鎖,JVM就會從鎖池中隨機取出一個執行緒,使這個執行緒擁有鎖,並且轉到就緒狀態。

2、 假如這個鎖沒有被其他執行緒佔用,本執行緒會獲得這把鎖,開始執行同步程式碼塊。 (一般情況下在執行同步程式碼塊時不會釋放同步鎖,但也有特殊情況會釋放物件鎖 如在執行同步程式碼塊時,遇到異常而導致執行緒終止,鎖會被釋放;在執行程式碼塊時,執行了鎖所屬物件的wait()方法,這個執行緒會釋放物件鎖,進入物件的等待池中)

相關文章