原子性
一個操作或者多個操作 要麼全部執行並且執行的過程不會被任何因素打斷,要麼就都不執行。
類似於數據庫的事務的原子性,比如在銀行轉賬時,兩個賬戶進行讀寫操作,若不具有原子性則可能導致意想不到的結果。
再舉個例子,一個簡單的賦值語句 i=9,假若執行到這句話包括兩個過程爲低16爲賦值和爲高16爲賦值。那麼有可能發生一個線程修改完低16位,被中斷。另外一個線程又去讀取i的值,這時候就會讀取到錯誤的數據。
可見性
當多個線程訪問同一個變量時,一個線程修改了這個變量的值,其他線程能夠立即看得到修改的值。
舉例:
//線程1執行的代碼
int i = 0;
i = 10;
//線程2執行的代碼
j = i;
假若線程1剛執行完i=10這句話還沒來得及寫回,線程2執行了 j=i,就會使得線程2拿到的 i 還是初始化的 0 而不是 10。
這就是可見性問題,線程1修改了 i 的值而線程2沒有立即看到值的變化。
有序性
程序執行的順序按照代碼的先後順序執行
這個問題主要是因爲 指令重排序,處理器爲了提高程序的執行效率,可能會在不影響執行結果的前提下調整語句的執行順序。舉個例子
int a = 10; //語句1
int r = 2; //語句2
a = a + 3; //語句3
r = a * a; //語句4
比如這段代碼,處理器可能會進行指令重排序,讓語句2在語句1前執行,但不會讓語句4在語句3之前執行,因爲那會影響執行結果。
但重排序只是保證了不影響單線程下執行結果,在多線程就有可能存在問題。假設一下情況:
//線程1:
context = loadContext(); //語句1
inited = true; //語句2
//線程2:
while(!inited ){
sleep()
}
doSomething(context);
在線程1中,語句1和語句2互相之間沒有依賴,可能會被重排序,使得語句2先執行,這時候如果線程2去判斷時會調出循環執行doSomething,而這時候context還未被初始化,會導致程序出錯。
爲了避免上述存在的不一致問題,JAVA虛擬機定義了一種java內存模型(Java Memory Model,JMM)來屏蔽各個硬件平臺和操作系統的內存訪問差異,以實現讓Java程序在各種平臺下都能達到一致的內存訪問效果。
原子性
在java中,對基本數據類型的變量的讀取和賦值(賦值 常量,而不包括賦值變量)都具有原子性的。
x = 10; //語句1
y = x; //語句2
x++; //語句3
x = x + 1; //語句4
上述語句中,只有語句1具有原子性。因爲其他的語句都涉及2-3個步驟才能完成,比如語句2需要先讀取x值再將值寫入到y相應的內存中。語句3需要先讀取x值,再加1,最後寫入。要想實現更大範圍操作的原子性,則需要通過synchronized或者lock來實現。
可見性
可見性,java通過volatile關鍵字來實現。
當一個共享變量被volatile修飾時,它會保證修改的值會被立即更新到內存,而讀取時也會直接從內存讀取新值。而普通的共享變量不能保證可見性,因爲普通共享變量被修改之後,什麼時候被寫入主存是不確定的,當其他線程去讀取時,此時內存中可能還是原來的舊值,因此無法保證可見性。
另外,通過synchronized和Lock也能夠保證可見性
有序性
在java中,可以通過volatile關鍵字來保證一定的“有序性”。另外可以通過synchronized和Lock來保證有序性。