標籤:更新 最佳化 and 學習 start blog read atomic count
1. Java記憶體模型(Java Memory Model, JMM)
Java的記憶體模型如下,所有變數都儲存在主記憶體中,每個線程都有自己的工作記憶體。
共用變數:如果一個變數在多個線程中都使用到了,那麼這個變數就是這幾個線程的共用變數。
可見度:一個線程對共用變數的修改,能夠及時地到主記憶體並且讓其他的線程看到。
怎麼理解上面的可見度的意思呢?
線程對共用變數的修改,只能在自己的工作記憶體裡操作,不能直接對主記憶體中的共用變數進行修改。而且一個線程不能直接存取另一個線程中的變數的值,只能通過主記憶體進行共用傳遞。
那麼就要求線程A對共用變數修改後,及時地更新到主記憶體中,線程B才可以及時地從主記憶體擷取最新的值到工作記憶體。
比如一個共用變數int i = 0; 線程A將其改為i =1; 其他線程此時擷取i的值,應該能及時地得到1,而不是0。
2. synchronized實現可見度
synchronized除了常見的原子性,還實現了可見度。這是因為:
1) 線程解鎖前,必須把共用變數的最新值重新整理到主記憶體中去;
2) 線程加鎖時,將清空工作記憶體中的共用變數的值,使用到共用變數時,從主記憶體中擷取最新的共用變數值(加鎖和解鎖需要同一把鎖)
3. volatile實現可見度
通過記憶體屏障和禁止重排序最佳化來實現可見度。
1) 對共用變數進行寫操作後,加入一條store屏障指令,強制將共用變數的值重新整理到主記憶體;
2) 對共用變數進行讀操作前,加入一條load屏障指令,強制從主記憶體中將最新值重新整理到工作記憶體;
4.volatile不能保證原子性
一個比較典型的例子是++運算子。
在下面的代碼中,一共建立了1000個線程,預期應該是加了1000次,那麼number的值應該是1000,實際上有可能並不是。
這是因為,++運算子並不是一次操作。以number++為例,可以看作是,先從主記憶體中取出number的值,然後將其加1,重新整理工作記憶體,重新整理主記憶體,這麼幾個步驟。
而volatile並不能保證原子性,這就意味著,有可能出現這種情況:
1)線程A擷取到主記憶體的number的值(假設為10)到工作記憶體
2)此時CPU調度,A暫停,線程B開始執行,同樣從主記憶體中擷取到number為10,number++後,number為11,重新整理到主記憶體
3)線程A繼續執行number++,它的工作記憶體中number為10,執行完畢重新整理到主記憶體,此時,number的值為11. 也就是說,AB兩個線程同時進行了+1操作,但最終的結果,只加了1
1 public class VolatileDemo { 2 3 private int number = 0; 4 5 public void increase() { 6 number++; 7 } 8 9 public int getNumber() {10 return number;11 }12 13 public static void main(String[] args) {14 final VolatileDemo demo = new VolatileDemo();15 16 for (int i = 0; i <= 999; i++) {17 new Thread(new Runnable() {18 @Override19 public void run() {20 demo.increase();21 }22 }).start();23 }24 25 //線程未執行完,主線程讓出CPU資源26 while(Thread.activeCount() > 1){27 Thread.yield();28 }29 30 //待上面的線程都執行完了,再列印,避免列印的不是最後的資料31 System.out.println(demo.getNumber());32 }33 }
5.volatile適用情境
1)對共用變數的寫操作,不依賴於其之前的值
不合適:number++, number = number * 2, number += 1等
合適:boolean值
2)該變數沒有包含在具有其他變數的不變式中,也就是說,不同的volatile變數之間,不能互相依賴
6.AtomicInteger實現遞增
上面我們已經知道一個整型的共用變數要實現遞增,如果使用++運算子,即使加上volatile關鍵字,也是無法保證其原子性的。而如果在訪問變數時加上synchronized塊,或者可重新進入鎖,開銷又太大。
JDK1.5之後,可以使用AtomicInteger進行遞增。該類是安全執行緒的。
將上面的代碼修改如下,就可以保證原子性和可見度。
1 import java.util.concurrent.atomic.AtomicInteger; 2 3 public class VolatileDemo { 4 5 private AtomicInteger number = new AtomicInteger(0); 6 7 public void increase() { 8 number.incrementAndGet(); 9 }10 11 public int getNumber() {12 return number.intValue();13 }14 15 public static void main(String[] args) {16 final VolatileDemo demo = new VolatileDemo();17 18 for (int i = 0; i <= 999; i++) {19 new Thread(new Runnable() {20 @Override21 public void run() {22 demo.increase();23 }24 }).start();25 }26 27 //線程未執行完,主線程讓出CPU資源28 while(Thread.activeCount() > 1){29 Thread.yield();30 }31 32 //待上面的線程都執行完了,再列印,避免列印的不是最後的資料33 System.out.println(demo.getNumber());34 }35 }
【慕課網學習筆記】Java共用變數的可見度和原子性