原子性
原子性指的是一個或者多個操作在 CPU 執行的過程中不被中斷的特性
在多線程情況下,線程會被操作系統調度進行任務切換,占有CPU時間片段的就執行,否則就阻塞
java中對基礎類型的變量賦值是原子性的,int a = 1 ;
但是像這種語句 count++; 在執行的時候,包含3的指令操作
指令 1:首先,需要把變量 count 從內存加載到 CPU的寄存器
指令 2:在寄存器中執行 +1 操作
指令 3:最后,將結果寫入內存
在一個線程里執行是沒有問題的,但是在多線程情況下,會引發數據不一致問題
對於上面的三條指令來說,如果線程 A 在指令 1 執行完后做線程切換,線程 A 和線程 B 按照下圖的序列執行,那么我們會發現兩個線程都執行了 count+=1 的操作,但是得到的結果不是我們期望的 2,而是 1。
可見性
可見性指的是當一個線程修改了共享變量后,其他線程能夠立即得知這個修改,volatile可以保證可見性
有序性
有序性指的是程序按照代碼的先后順序執行
為了性能優化,編譯器和處理器會可能進行指令重排序,有時候會改變程序中語句的先后順序
a = 5; //1 b = 20; //2 c = a + b; //3
編譯器優化后可能變成
b = 20; //1 a = 5; //2 c = a + b; //3
編譯器調整了語句的順序,在單線程中並沒有什么影響,但是在多線程中可能會出現異常
單例模式有一種實現是
public class Singleton { static Singleton instance; static Singleton getInstance(){ if (instance == null) { synchronized(Singleton.class) { if (instance == null) instance = new Singleton(); } } return instance; } }
我們先看 instance = new Singleton()
的未被編譯器優化的操作
- 指令 1:分配一塊內存 M;
- 指令 2:在內存 M 上初始化 Singleton 對象;
- 指令 3:然后 M 的地址賦值給 instance 變量。
編譯器優化后的操作指令
- 指令 1:分配一塊內存 M;
- 指令 2:將 M 的地址賦值給 instance 變量;
- 指令 3:然后在內存 M 上初始化 Singleton 對象。
現在有A,B兩個線程,我們假設線程A先執行getInstance()
方法,當執行編譯器優化后的操作指令2
時(此時候未完成對象的初始化),這時候發生了線程切換,那么線程B進入,剛好執行到第一次判斷instance == null 會發現instance
不等於null了,所以直接返回instance,而此時instance是沒有初始化的