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 @Override 19 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 @Override 21 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 }