java併發編程實戰-volatile

https://blog.csdn.net/u014108122/article/details/38173201java

非原子的64位操做:緩存

                    非volatile類型的64位數值變量(double和long)。java內存模型要求,變量的讀取操做和寫入操做都必須是原子操做,但對於非volatile類型的long和double類型,jvm容許將64位的讀操做和寫操做分解爲2個32位操做。當讀取一個非volatile類型的long變量時,若是該變量的讀寫操做在不一樣的線程中執行,頗有可能讀取到某個值得高32位和另外 一個值的低32位。安全

                    所以,在多線程程序中使用共享且可變的long和double變量也是不安全的,除非用volatile聲明,或者用鎖保護起來多線程

volatile變量是一種比sychronized關鍵字更輕量的同步機制併發

volatile變量:編譯器與運行時都會注意到這個變量是共享的,由於不會將該變量上的操做與其餘內存操做一塊兒重排序,volatile變量不會被緩存在寄存器或者對其餘處理器不可見的地方,所以在讀取volatile變量時總會返回最新寫入的值jvm

volatile變量使用場景:  一般用作某個操做完成,發生中斷或者狀態的標誌ide

當且知足如下全部條件,才應該使用volatile變量優化

                    1.在訪問變量時不須要加鎖this

                    2.該變量不會與其餘狀態變量一塊兒歸入不變性條件中spa

                    3.對變量的寫入操做不依賴變量的當前值,或者你能確保只有單個線程更新變量的值

加鎖機制既能夠保證可見性又能夠保證原子性,而volatile變量只能確保可見性

原子性

原子是世界上的最小單位,具備不可分割性。好比 a=0;(a非long和double類型) 這個操做是不可分割的,那麼咱們說這個操做時原子操做。再好比:a++; 這個操做實際是a = a + 1;是可分割的,因此他不是一個原子操做。非原子操做都會存在線程安全問題,須要咱們使用同步技術(sychronized)來讓它變成一個原子操做。一個操做是原子操做,那麼咱們稱它具備原子性。java的concurrent包下提供了一些原子類,咱們能夠經過閱讀API來了解這些原子類的用法。好比:AtomicInteger、AtomicLong、AtomicReference等。

可見性

可見性,是指線程之間的可見性,一個線程修改的狀態對另外一個線程是可見的。也就是一個線程修改的結果。另外一個線程立刻就能看到。好比:用volatile修飾的變量,就會具備可見性。volatile修飾的變量不容許線程內部緩存和重排序,即直接修改內存。因此對其餘線程是可見的。可是這裏須要注意一個問題,volatile只能讓被他修飾內容具備可見性,但不能保證它具備原子性。好比 volatile int a = 0;以後有一個操做 a++;這個變量a具備可見性,可是a++ 依然是一個非原子操做,也就這這個操做一樣存在線程安全問題。

他們之間關係

原子性是說一個操做是否可分割。可見性是說操做結果其餘線程是否可見。這麼看來他們其實沒有什麼關係。

 

案例:

  1. package com.chu.test.thread;  
  2. /** 
  3.  * 可見性分析 
  4.  * @author Administrator 
  5.  * 
  6.  *volatile 會拒絕編譯器對其修飾的變量進行優化。也就不會存在重排序的問題。volatile只會影響可見性,不會影響原子性。 
  7.  *下面程序若是不加 
  8.  */  
  9. public class Test {  
  10.   
  11.     volatile int a = 1;  
  12.     volatile boolean ready;  
  13.       
  14.     public class PrintA extends Thread{  
  15.         @Override  
  16.         public void run() {  
  17.             while(!ready){  
  18.                 Thread.yield();  
  19.             }  
  20.             System.out.println(a);  
  21.         }  
  22.     }  
  23.     public static void main(String[] args) throws InterruptedException {  
  24.         Test t = new Test();  
  25.         t.new PrintA().start();  
  26.         //下面兩行若是不加volatile的話,執行的前後順序是不可預測的。而且下面兩行都是原子操做,可是這兩行做爲一個總體的話就不是一個原子操做。  
  27.         t.a = 48; //這是一個原子操做,可是其結果不必定具備可見性。加上volatile後就具有了可見性。  
  28.         t.ready = true;//同理  
  29.     }  
  30.   
  31.  

  32.  

    來源: http://blog.csdn.net/maosijunzi/article/details/18315013

  在說明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變量的原子性,例如:

[java] view plain copy

  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(含視頻和代碼)

【相關習題】

 

(1)下列說法不正確的是()

A.當兩個併發線程訪問同一個對象object中的這個synchronized(this)同步代碼塊時,一個時間內只能有一個線程獲得執行。

B.當一個線程訪問object的一個synchronized(this)同步代碼塊時,另外一個線程仍然能夠訪問該object中的非synchronized(this)同步代碼塊。

C.當一個線程訪問object的一個synchronized(this)同步代碼塊時,其餘線程對object中全部其它synchronized(this)同步代碼塊的訪問不會被阻塞。

D.當一個線程訪問object的一個synchronized(this)同步代碼塊時,它就得到了這個object的對象鎖。結果,其它線程對該object對象全部同步代碼部分的訪問都被暫時阻塞。

答案:C,當一個線程訪問object的一個synchronized(this)同步代碼塊時,其餘線程對object中全部其它synchronized(this)同步代碼塊的訪問將會被阻塞。

(2)下面敘述錯誤的是:

A.經過synchronized和volatile均可以實現可見性

B.不一樣線程之間能夠直接訪問其餘線程工做內存中的變量

C.線程對共享變量的全部操做都必須在本身的工做內存中進行

D.全部的變量都存儲在主內存中

答案:B,不一樣線程之間沒法直接訪問其餘線程工做內存中的變量

來源: http://blog.csdn.net/guyuealian/article/details/52525724

原子性AtomicInteger 實現說明:http://blog.csdn.net/samjustin1/article/details/52254636

相關文章
相關標籤/搜索