原創做品,能夠轉載,可是請標註出處地址:http://www.cnblogs.com/V1haoge/p/7833881.htmlhtml
一、volatile簡述java
聽說,volatile是java語言中最輕量級的併發控制方式。緩存
volatile能夠實現可見性、有序性,可是沒法實現原子性,相對來講:synchronized能夠實現這三個併發特性,因此咱們可使用synchronized來代替volatile,可是一直以來synchronized都已重量級聞名,其實在jdk1.5以後的版本中,java對synchronized進行了針對性優化處理,其操做速度已經再也不是制約其是否可選擇的因素,如今通常經過實際的狀況來決定使用哪一種方式。多線程
二、volatile特性併發
2.1 可見性高併發
volatile能夠保證變量的可見性,指的是什麼呢?優化
可見性指的是在某一線程中對變量進行修改以後,其餘線程能夠當即發現並使用這個修改(一個線程的修改對其餘線程可見)。atom
可見性的實現方式:volatile經過對java內存模型中主內存和工做內存交互方式的控制來實現。volatile確保一個線程對其修飾的變量的更改當即寫入到主內存,同時確保每一次針對其修飾變量的讀取操做直接從主內存中獲取(即volatile強制將assign賦值操做和store、write操做綁定在一塊兒,將use使用操做強制和read、load操做綁定在一塊兒,這樣assign以後必須執行store、write操做,use操做以前必須先執行read、load操做)這樣就確保了其餘線程讀取到的變量的值是最新的(不熟悉這幾個操做的同窗請先了解java內存模型)。spa
Volatile更底層的實現方式:一個線程對volatile變量進行了修改以後,會寫到工做內存,這一步映射到底層就是cpu將計算結果保存到高速緩存中,這時會觸發一個LOCK指令,這個指令有兩個做用,第一,它會鎖定總線或者緩存,將修改後的新值保存到系統內存中,映射到JVM就是保存到主內存中。第二,它會將其餘CPU的高速緩存中的這個變量的值置爲無效,映射到JVM就是講其餘線程的工做內存中保存的這個變量值置爲無效,這樣在這些其餘線程要對變量進行操做時,讀取變量時發現工做內存中的值是無效的,隨即從主內存從新讀取,並保存到工做內存。線程
這裏還要說說明一點,不管是主內存仍是工做內存(系統內存仍是高速緩存)都只是保存數據的部件或位置,全部針對變量的操做所有須要在CPU中進行,因此即便將數據從主內存(系統內存)讀取到工做內存(高速緩存)中以後,想要操做,還須要從工做內存(高速緩存)中讀取到CPU中的寄存器中進行計算。
2.2 非原子性
注意:volatile能夠實現可見性,可是沒法實現原子性。單個volatile變量的讀寫操做具備原子性,可是複合操做是與java代碼相關的,並非volatile這麼一個關鍵字既能夠控制得了的。(請將可見性和原子操做區分開來,我以前就混淆在一塊兒,分開以後當即通透了)
java中實現原子操做的方式仍是有不少的,可是並不包含volatile,簡單的實現方式有:atomic包下的原子操做(經過CAS實現),基本數據類型的讀寫操做,加鎖(synchronized或者Lock)實現等。
java中經典的非原子操做如自增實現,普通的i++操做看似只有一句話,可是編譯成機器指令以後擁有多少行,不可知,確定不是一句就能實現的,這麼多命令要執行固然沒法保證原子性,這時候咱們能夠用AtomicInteger和AtomicLong原子操做類的getAndIncrement()方法來實現,固然是用synchronized加鎖一樣能夠實現。
2.3 有序性
volatile的另外一個做用就是避免重排序優化,使用內存屏障的方式來實現禁止重排序優化。在單線程環境中固然沒有必要禁止重排序優化,可是在多線程環境中指令重排序後執行就可能會出錯,好比線程A中須要檢測線程B中的某一個變量的值,依據這個值來進行某些操做。若是沒有使用volatile修飾該變量,線程B中針對這個變量的操做就可能會發生重排序,可能會提早執行,這時一旦操做提早執行,那麼線程A就能夠會提早獲得這個變量的值(或許是在一些線程A的準備工做還未所有準備好的狀況,假設這些準備工做在線程B中定義,可是與變量操做無依賴關係,一旦變量操做提早,這些準備工做就會滯後,這時線程A就會在準備工做還沒有完成的狀況下啓動執行後行代碼,致使出錯)。爲變量加上volatile修飾以後,就會禁止其操做的指令重排序優化,保證因此的準備工做所有執行完成以後再進行變量操做,而後線程A在準備齊備的狀況下啓動,得以正常執行。
volatile有序性的實現原理是什麼呢?
volatile底層經過內存屏障的方式來禁止重排序優化,具體來講,JMM採用的是保守策略,所謂保守策略,就是經過冗餘的內存屏障來保證全部影響Volatile功能的重排序所有被禁止,保證volatile功能的完整性,此處冗餘的意思就是,可能會存在多餘的內存屏障,但這種多餘的內存屏障是不影響操做執行的,或者說是有的內存屏障所禁止的重排序操做若是實際發生了重排序也不會影響操做結果的狀況,可是這種冗餘的內存屏障能夠排除那種任何特殊狀況來確保volatile功能的完整性。
內存屏障包括:
(1)volatile變量寫操做以前的storestore屏障,這個屏障保證全部在volatile寫操做以前的任何操做都不能被重排序到volatile寫操做以後,確保volatile變量寫操做的正確性,由於全部直接或間接的修改都在寫操做以前完成了,那麼寫的變量值必定是最終的正確值。
(2)volatile變量寫操做以後的storeload屏障,這個屏障保證全部在volatile寫操做以後的任何操做都不能被重排序到volatile寫操做以前,確保volatile變量寫操做的正確性,其實這裏真正禁止的是其後可能出現的volatile讀寫操做被重排序到這個寫操做以前。
(3)volatile變量讀操做以後的loadload屏障,這個屏障保證全部在volatile讀操做以後的任何讀操做都不會被重排序到volatile讀操做以前,確保volatile變量讀操做的正確性。
(4)volatile變量讀操做以後的loadstore屏障,這個屏障保證全部在volatile讀操做以後的任何寫操做都不會被重排序到volatile讀操做以前,確保volatile變量讀操做的正確性。
上面的內容並很差理解和記憶,咱們能夠總結以下:
全部Volatile寫操做以前的操做禁止重排序到該寫操做以後;
全部volatile讀操做以後的操做禁止重排序到該讀操做以前;
volatile寫操做以後是volatile讀操做時,禁止重排序。
記住上述三點就能夠了!
三、volatile使用
在涉及到併發操做的狀況下,能夠優先考慮是否可使用volatile來解決問題,適合的場景以下:
在只涉及可見性,針對變量的操做只是簡單的讀寫(保證操做的原子性)的狀況下可使用volatile來解決高併發問題,若是這時針對變量的操做是非原子的操做,這時若是隻是簡單的i++式的操做,可使用原子類atomic類來保證操做的原子性(採用CAS實現),若是是複雜的業務操做,那麼捨棄volatile,採用鎖來解決併發問題(synchronized或者Lock)。
四、volatile總結
volatile能夠實現可見性和有序性,沒法實現原子性 ,簡單的原子操做能夠委託給其餘方式進行實現,複雜的原子操做須要藉助鎖來實現,這時候徹底沒有必要加上volatile了,由於鎖已經包含了volatile的功能。