轉載請注明原文地址:http://www.cnblogs.com/ygj0930/p/6536131.html
一:現代計算機的高速緩存
在計算機組成原理中講到,現代計算機為了匹配 計算機存儲設備的讀寫速度 與 處理器運算速度,在CPU和內存設備之間加入了一個名為Cache的高速緩存設備來作為緩沖:將運算需要用到的數據從內存復制到cache中,CPU可以在運算期間對cache進行高速的讀寫操作,運算結束后在從cache把數據同步回內存。
Cache引出了一個新問題:緩存一致性。每個處理器有自己的cache,而他們又共享一個主內存。當多個處理器的運算任務都設計同一內存區域時,將會導致各自的緩存數據不一致。那么,在各自運算結束后,以誰的緩存數據為准同步回主存呢?此時,就需要 緩存一致性協議 來制定多個處理器對統一內存區域的讀寫操作了。
除了Cache,為了更好地利用處理器資源,處理器還會對代碼進行 亂序執行 ,在最后才把結果重組使得結果與順序執行一致。
二:Java內存模型的類比
類比與現代計算機的主存與cache,JVM中規定了 所有變量都存儲在主內存中(類比計算機的主存),然后每條線程有自己的工作內存(類比每個處理器的cache)。線程的工作內存中保存了該線程需要用到的變量的拷貝值,線程在CPU上運行時都是對自己工作線程中的數據進行讀寫操作,運行結束之后才把數據同步化主內存中。那么類比於計算機使用 緩存一致性協議 解決緩存一致性問題,JVM中就需要線程同步機制來達到多線程對同一內存區域的讀寫控制了。
此外,Java編譯器為了提高性能,采取了 指令重排序(類比計算機的 亂序執行),若多個線程都有語句對同一內存區域進行操作的話,有可能因為指令重排序而導致結果不符預料。因此,也需要線程同步機制來達到多線程對同一內存區域的讀寫控制。
三:主內存與工作內存的數據交互
JVM定義了8種操作來完成主內存與線程工作內存的數據交互:
1:lock:把主內存變量標識為一條線程獨占,此時不允許其他線程對此變量進行讀寫。
2:unlock:解鎖一個主內存變量。
3:read:把一個主內存變量值讀入到線程的工作內存,強調的是讀入這個過程。
4:load:把read到變量值保存到線程工作內存中作為變量副本,強調的是讀入的值的保存過程。
5:use:線程執行期間,把工作內存中的變量值傳給字節碼執行引擎。
6:assign(賦值):字節碼執行引擎把運算結果傳回工作內存,賦值給工作內存中的結果變量。
7:store:把工作內存中的變量值傳送到主內存,強調傳送的過程。
8:write:把store傳送進來的變量值寫入主內存的變量中,強調保存的過程。
JVM要求以上8個操作都具有原子性,即對數據的讀寫操作具有原子性。但也有例外,即:long、double的非原子性協定:這兩個64位類型的數據的讀、寫操作各需兩次進行,一次讀/寫 32 位,這兩次讀/兩次寫 是不保證原子性的。
四:原子性、可見性、有序性
原子性:基本數據類型的讀寫操作是原子性的;更大范圍的(代碼塊)的原子性可以用lock、unlock操作來實現(上鎖后就只有一個線程來執行了,所以不會被其他線程打斷原子操作),表現到代碼層面就是使用syncrhoized同步塊。
可見性:當一個線程修改了被多線程共享的一個主內存變量值時,其他線程能立刻知道這個修改。
我們在上面可以知道,JVM是通過工作內存中的變量值變化后,把新值同步會主內存,然后其他線程從主內存讀取這個新值來實現可見性的。這里有個區別:普通變量的值變化后,不一定會立刻同步會主內存,而是會等線程執行完或者一段時間后才同步會,而且同步回主內存后,其他線程的工作內存也不一定會立刻讀取新值。而被volatile關鍵字修飾的變量,一旦在工作內存中被修改,則立刻同步回主內存,並且其他使用了這個變量的線程的工作內存會立刻從主內存讀取新值。而syncrhoized關鍵字修飾的變量由於一次只能有一個線程能使用,故一次也只能有一個工作線程讀寫它,所以也能“縱向”地實現可見性。
有序性:多線程之間對共享數據的操作的有序性,可以通過volatile和syncrhoized關鍵字來保證。volatile關鍵字禁止了指令重排序,而syncrhoized關鍵字規定了多個線程每次只能有一個線程對共享數據進行操作。
五:volatile關鍵字
一個volatile變量具有兩種特性:可見性、禁止指令重排序。但是,volatile不具備原子性!原因是volatile變量的值可以被多線程交替修改,而修改包括了read、load、use、store、write等過程,這些過程不能保證是原子執行的。
可見性:被volatile關鍵字修飾的變量,一旦在工作內存中被修改,則立刻同步回主內存,並且其他使用了這個變量的線程的工作內存會立刻從主內存讀取新值。
禁止指令重排序:volatile變量在賦值后會創建一個內存屏障:指令重排序時,位於后面的指令不能排到內存屏障之前。