【java】內存可見性和原子性:Synchronized和Volatile的比較

在說明Java多線程內存可見性之前,先來簡單瞭解一下Java內存模型。

     (1)Java所有變量都存儲在主內存中
     (2)每個線程都有自己獨立的工作內存,裏面保存該線程的使用到的變量副本(該副本就是主內存中該變量的一份拷貝)

   (1)線程對共享變量的所有操作都必須在自己的工作內存中進行,不能直接在主內存中讀寫
   (2)不同線程之間無法直接訪問其他線程工作內存中的變量,線程間變量值的傳遞需要通過主內存來完成。
線程1對共享變量的修改,要想被線程2及時看到,必須經過如下2個過程:
   (1)把工作內存1中更新過的共享變量刷新到主內存中
   (2)將主內存中最新的共享變量的值更新到工作內存2中

可見性與原子性

   可見性:一個線程對共享變量的修改,更夠及時的被其他線程看到
   原子性:即不可再分了,不能分爲多步操作。比如賦值或者return。比如"a = 1;"和 "return a;"這樣的操作都具有原子性。類似"a += b"這樣的操作不具有原子性,在某些JVM中"a += b"可能要經過這樣三個步驟:
① 取出a和b
② 計算a+b
③ 將計算結果寫入內存

 

(1)Synchronized:保證可見性和原子性
    Synchronized能夠實現原子性和可見性;在Java內存模型中,synchronized規定,線程在加鎖時,先清空工作內存→在主內存中拷貝最新變量的副本到工作內存→執行完代碼→將更改後的共享變量的值刷新到主內存中→釋放互斥鎖


(2)Volatile:保證可見性,但不保證操作的原子性
    Volatile實現內存可見性是通過store和load指令完成的;也就是對volatile變量執行寫操作時,會在寫操作後加入一條store指令,即強迫線程將最新的值刷新到主內存中;而在讀操作時,會加入一條load指令,即強迫從主內存中讀入變量的值。但volatile不保證volatile變量的原子性,例如:


 
  1. Private int Num=0;

  2. Num++;//Num不是原子操作

    Num不是原子操作,因爲其可以分爲:讀取Num的值,將Num的值+1,寫入最新的Num的值。
    對於Num++;操作,線程1和線程2都執行一次,最後輸出Num的值可能是:1或者2
   【解釋輸出結果1的解釋:當線程1執行Num++;語句時,先是讀入Num的值爲0,倘若此時讓出CPU執行權,線程獲得執行,線程2會重新從主內存中,讀入Num的值還是0,然後線程2執行+1操作,最後把Num=1刷新到主內存中; 線程2執行完後,線程1由開始執行,但之前已經讀取的Num的值0,所以它還是在0的基礎上執行+1操作,也就是還是等於1,並刷新到主內存中。所以最終的結果是1

    一般在多線程中使用volatile變量,爲了安全,對變量的寫入操作不能依賴當前變量的值:如Num++或者Num=Num*5這些操作。


(3)Synchronized和Volatile的比較
    1)Synchronized保證內存可見性和操作的原子性
    2)Volatile只能保證內存可見性
    3)Volatile不需要加鎖,比Synchronized更輕量級,並不會阻塞線程(volatile不會造成線程的阻塞;synchronized可能會造成線程的阻塞。)
    4)volatile標記的變量不會被編譯器優化,而synchronized標記的變量可以被編譯器優化(如編譯器重排序的優化).
    5)volatile是變量修飾符,僅能用於變量,而synchronized是一個方法或塊的修飾符。
      volatile本質是在告訴JVM當前變量在寄存器中的值是不確定的,使用前,需要先從主存中讀取,因此可以實現可見性。而對n=n+1,n++等操作時,volatile關鍵字將失效,不能起到像synchronized一樣的線程同步(原子性)的效果。

 

【參考資料】《細說Java多線程之內存可見性》http://www.imooc.com/video/6775(含視頻和代碼)

 

轉載:http://blog.csdn.net/guyuealian/article/details/52525724

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章