三大特性: 原子性、可見性、有序性
什麼是原子性
即一個操作或者多個操作 要麼全部執行并且執行的過程不會被任何因素打斷,要麼就都不執行。
一個很經典的例子就是銀行賬戶轉賬問題:
比如從賬戶A向賬戶B轉1000元,那麼必然包括2個操作:從賬戶A減去1000元,往賬戶B加上1000元。這2個操作必須要具備原子性才能保證不出現一些意外的問題。
我們操作資料也是如此,比如i = i+1;其中就包括,讀取i的值,計算i,寫入i。這行代碼在Java中是不具備原子性的,則多線程運作肯定會出問題,是以也需要我們使用同步和lock這些東西來確定這個特性了。
原子性其實就是保證資料一緻、線程安全一部分。
什麼是可見性
當多個線程通路同一個變量時,一個線程修改了這個變量的值,其他線程能夠立即看得到修改的值。
若兩個線程在不同的cpu,那麼線程1改變了i的值還沒重新整理到主存,線程2又使用了i,那麼這個i值肯定還是之前的,線程1對變量的修改線程沒看到這就是可見性問題。
可見性補充說明:
1.多個線程同時修改全局變量,其中一個線程修改後還沒有重新整理到主記憶體就被另外一個線程拿走使用,會産生髒資料。
2.其中一個線程修改後已經重新整理到主記憶體,但是另外一個線程有延遲,沒有拿到主記憶體的值,還是用之前本地記憶體裡邊的值,也會産生髒資料。
3.Thread.sleep()隻是為了讓效果明顯,可以看出效果。
4.volatile關鍵字就是保證全局變量可以實時同步的重新整理到主記憶體,并且直接分派到本地記憶體副本中,實作可見及同步。
什麼是有序性
程式執行的順序按照代碼的先後順序執行。
一般來說處理器為了提高程式運作效率,可能會對輸入代碼進行優化,它不保證程式中各個語句的執行先後順序同代碼中的順序一緻,但是它會保證程式最終執行結果和代碼順序執行的結果是一緻的。如下:
int a = 10; //語句1
int r = 2; //語句2
a = a + 3; //語句3
r = a*a; //語句4
則因為重排序,他還可能執行順序為 2-1-3-4,1-3-2-4
但絕不可能 2-1-4-3,因為這打破了依賴關系。
顯然重排序對單線程運作是不會有任何問題,而多線程就不一定了,是以我們在多線程程式設計時就得考慮這個問題了。
Java記憶體模型
共享記憶體模型指的就是Java記憶體模型(簡稱JMM),JMM決定一個線程對共享變量的寫入時,能對另一個線程可見。從抽象的角度來看,JMM定義了線程和主記憶體之間的抽象關系:線程之間的共享變量存儲在主記憶體(main memory)中,每個線程都有一個私有的本地記憶體(local memory),本地記憶體中存儲了該線程以讀/寫共享變量的副本。本地記憶體是JMM的一個抽象概念,并不真實存在。它涵蓋了緩存,寫緩沖區,寄存器以及其他的硬體和編譯器優化。

從上圖來看,線程A與線程B之間如要通信的話,必須要經曆下面2個步驟:
1. 首先,線程A把本地記憶體A中更新過的共享變量重新整理到主記憶體中去。
2. 然後,線程B到主記憶體中去讀取線程A之前已更新過的共享變量。
下面通過示意圖來說明這兩個步驟:
如上圖所示,本地記憶體A和B有主記憶體中共享變量x的副本,也就是說本地記憶體副本裡邊的初始值都是來自主記憶體的值。假設初始時,這三個記憶體中的x值都為0。線程A在執行時,把更新後的x值(假設值為1)臨時存放在自己的本地記憶體A中。當線程A和線程B需要通信時,線程A首先會把自己本地記憶體中修改後的x值重新整理到主記憶體中,此時主記憶體中的x值變為了1。随後,線程B到主記憶體中去讀取線程A更新後的x值,此時線程B的本地記憶體的x值也變為了1。
從整體來看,這兩個步驟實質上是線程A在向線程B發送消息,而且這個通信過程必須要經過主記憶體。JMM通過控制主記憶體與每個線程的本地記憶體之間的互動,來為java程式員提供記憶體可見性保證。
總結:什麼是Java記憶體模型:java記憶體模型簡稱jmm,定義了一個線程對另一個線程可見。共享變量存放在主記憶體中,每個線程都有自己的本地記憶體,當多個線程同時通路一個資料的時候,可能本地記憶體沒有及時重新整理到主記憶體,是以就會發生線程安全問題。