【慕課網學習筆記】Java共用變數的可見度和原子性

來源:互聯網
上載者:User

標籤:更新   最佳化   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共用變數的可見度和原子性

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.