線程切換會帶來原子性的問題java
int i = 1; // 原子操做 i++; // 非原子操做,從主內存讀取 i 到線程工做內存,進行 +1,再把 i 寫到主內存。
雖然讀取和寫入都是原子操做,但合起來就不屬於原子操做,咱們又叫這種爲「複合操做」。編程
咱們能夠用synchronized 或 Lock 來把這個複合操做「變成」原子操做。緩存
例子:多線程
//使用synchronized private synchronized void increase(){ i++; } //使用Lock private int i = 0; Lock mLock = new ReentrantLock(); private void increase() { mLock.lock(); try { i++; } finally{ mLock.unlock(); } }
這樣咱們就能夠把這個一個方法看作一個總體,一個不可分割的總體。併發
除此以前,咱們還能夠用java.util.concurrent.atomic裏的原子變量類,能夠確保全部對計數器狀態訪問的操做都是原子的。優化
例子:atom
AtomicInteger mAtomicInteger = new AtomicInteger(0); private void increase(){ mAtomicInteger.incrementAndGet(); }
緩存致使可見性問題線程
int v = 0; // 線程 A 執行 v++; // 線程 B 執行 System.out.print("v=" + v);
即便是在執行完線程裏的 i++ 後再執行線程 B,線程 B 的輸入結果也會有 2 個種狀況,一個是 0 和1。code
由於 i++ 在線程 A(CPU-1)中作完了運算,並無馬上更新到主內存當中,而線程B(CPU-2)就去主內存當中讀取並打印,此時打印的就是 0。blog
禁用緩存能保證可見性,volatile關鍵字能夠禁用緩存
synchronized和Lock可以保證可見性。
致使有序性的緣由是編譯優化
咱們都知道處理器爲了擁有更好的運算效率,會自動優化、排序執行咱們寫的代碼,但會確保執行結果不變。
例子:
int a = 0; // 語句 1 int b = 0; // 語句 2 i++; // 語句 3 b++; // 語句 4
這一段代碼的執行順序頗有可能不是按上面的 一、二、三、4 來依次執行,由於 1 和 2 沒有數據依賴,3 和 4 沒有數據依賴, 二、一、四、3 這樣來執行能夠嗎?徹底沒問題,處理器會自動幫咱們排序。
在單線程看來並無什麼問題,但在多線程則很容易出現問題。
再來個例子:
// 線程 1 init(); inited = true; // 線程 2 while(inited){ work(); }
init(); 與 inited = true; 並無數據的依賴,在單線程看來,若是把兩句的代碼調換好像也不會出現問題。
但此時處於一個多線程的環境,而處理器真的把這兩句代碼從新排序,那問題就出現了,若線程 1 先執行 inited = true; 此時,init() 並無執行,線程 2 就已經開始調用 work() 方法,此時極可能形成一些奔潰或其餘 BUG 的出現。
synchronized和Lock能確保原子性,能讓多線程執行代碼的時候依次按順序執行,天然就具備有序性。
而volatile關鍵字也能夠解決這個問題,volatile 關鍵字能夠保證有序性,讓處理器不會把這行代碼進行優化排序。
**** 碼字不易若是對你有幫助請給個關注****
**** 愛技術愛生活 QQ羣: 894109590****