violate能保證可見性但沒法提供操做的原子性

簡單瞭解一下Java內存模型。

  1. Java全部變量都存儲在主內存中
  2. 每一個線程都有本身獨立的工做內存,裏面保存該線程的使用到的變量副本(該副本就是主內存中該變量的一份拷貝)
  3. 線程對共享變量的全部操做都必須在本身的工做內存中進行,不能直接在主內存中讀寫
  4. 不一樣線程之間沒法直接訪問其餘線程工做內存中的變量,線程間變量值的傳遞須要經過主內存來完成。
  5. 線程1對共享變量的修改,要想被線程2及時看到,必須通過以下2個過程:
    • 把工做內存1中更新過的共享變量刷新到主內存中
    • 將主內存中最新的共享變量的值更新到工做內存2中

可見性與原子性

可見性:一個線程對共享變量的修改,更夠及時的被其餘線程看到安全

原子性:即不可再分了,不能分爲多步操做。好比賦值或者return。好比"a = 1;"和 "return a;"這樣的操做都具備原子性。相似"a += b"這樣的操做不具備原子性,在某些JVM中"a += b"可能要通過這樣三個步驟:多線程

1. 取出a和b
2. 計算a+b
3. 將計算結果寫入內存
複製代碼

Synchronized:保證可見性和原子性

Synchronized可以實現原子性和可見性;在Java內存模型中,synchronized規定,線程在加鎖(對象鎖,類鎖)時的工做步驟:函數

  1. 先清空工做內存
  2. 在主內存中拷貝最新變量的副本到工做內存
  3. 執行完代碼
  4. 將更改後的共享變量的值刷新到主內存中
  5. 釋放互斥鎖。

PS:優化

  1. 當synchronized修飾一個static方法時,多線程下,獲取的是類鎖(即Class自己,注意:不是實例);spa

  2. 當synchronized修飾一個非static方法時,多線程下,獲取的是對象鎖(即類的實例對象)線程

Volatile:保證可見性,但不保證操做的原子性

  1. Volatile實現內存可見性是經過內存屏障store和load指令完成的;code

  2. 對volatile變量執行寫操做時,會在寫操做後加入一條store指令,即強迫線程將最新的值刷新到主內存中;對象

  3. 而在讀操做時,會加入一條load指令,即強迫從主內存中讀入變量的值。但volatile不保證volatile變量的原子性,例如:排序

    Private int Num=0;
     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這些操做。

  1. 在雙重檢查單例模式下使用 Volatile 來保證線程安全的原理就是利用了 Volatile 修飾的變量由於插入了內存屏障,禁止了指令重排,從而保證了有序性.即 User user = new User()這個操做,本來的是會被分解爲三個操做:

    (a)給Singleton的實例分配內存
     (b)調用Singleton()的構造函數,初始化成員字段;
     (c)將singleton對象指向分配的內存空間(即singleton不爲空了);
    複製代碼

變量加上volatile能夠防止對象初始化的無序性,對象必須初始化完成後纔將地址返回。

Synchronized和 Volatile 的比較

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