volatile語義

volatile變量

​ 這是Java提供的一種弱同步機制;volatile變量有2種語義。html

  • volatile變量對全部線程都可見
  • volatile變量禁止指令重排序

volatile變量對全部線程都可見

​ 可見性是指:一條線程改變了變量的值,其餘線程都能知道。java

在解釋這個規則原理以前,先對內存可見性作必定了解:編程

http://www.javashuo.com/article/p-snvjefhh-hs.html安全

java工做線程的內存稱爲線程的工做內存,線程之間的內存是獨立的(Java棧空間是屬於線程的,正是因爲內存獨立),線程之間經過主內存進行信息交換。多線程

​ 正是因爲線程之間內存獨立,而數據操做都是在線程的工做內存中進行。那麼普通的變量就會出現併發安全問題。在2個線程同時從主內存中加載出來了a=1,而且線程1進行操做a=a+1;線程2進行操做a=a+2;那麼最後主內存中的數據中,a究竟是多少呢??併發

​ 這取決於到底線程1仍是線程2最後寫入,確定是後寫入的值生效,將覆蓋前面的值。app

那麼volatile變量是如何作到全部線程都能知道最新的值的呢?優化

​ 如你所想;volatile變量在使用前,會進行刷新。線程

在2個線程同時從主內存中加載出來了a=1,而且線程1進行操做a=a+1;線程2進行操做a=a+2;那麼最後主內存中的數據中,a究竟是多少呢??code

​ volatile變量的操做過程以下:線程1在執行a= a+1時,先刷新a的值,即從新從主內存中獲取a的值;執行完以後,將a的值寫入到主內存(馬上寫入,不會重排序)。線程2在執行a=a+2時,也要先刷新a的值,這時假設a已經被線程1改變,那麼線程2在執行以前,就會將a更新爲新的值。保證了變量a的操做正確性!

volatile變量禁止指令重排序

​ 普通變量能保證最後的結果如程序代碼所描述,但不能保證底層實際的執行順序如程序所寫。

在解釋這個規則原理以前,先對指令重排序作必定了解:

http://www.javashuo.com/article/p-snvjefhh-hs.html

指令重排序出現的前提:JIT對字節碼進行編譯獲得彙編代碼。因此若是程序運行在純解釋器環境(呵,這確定是不可能的),是不存在重排序的現象的,畢竟都沒有編譯。

而將字節碼編譯成彙編,JIT會使用分層機制對須要編譯的代碼進行優化(也不扯太遠),最終的結果是:編譯成的彙編代碼,跟肉眼可見的Java代碼順序不必定一致。詳情見見上文博客;那是由於JIT會根據狀況將代碼打亂重分配,只要保證最後的結果符合happens-before原則。即不破壞程序的有序性!

​ 而使用volatile修飾的變量,編譯器和運行時都會注意這個變量是共享的,所以不會將該變量上的操做與其餘操做儀器重排序。

​ 那麼是爲何呢,怎麼才能作到不對它進行重排序呢。答案是採用內存屏障(Memory Barrier或Memory Fence)。對volatile變量的操做,都將使用內存屏障,而重排序時,不能將volatile變量後的操做排序到屏障以前。

內存屏障相關:若是瞭解內存屏障,推薦

  • 《Java併發編程的藝術》
  • 百度:內存屏障

都是些要記的東西,記住原理便可:重排序時不會打亂順序。

總結

​ 從volatile的語義咱們知道,volatile並非保證線程安全。而是變量共享!若是隻有一個線程修改volatile變量,那麼其餘線程都能讀到正確的值。

​ 假設多線程同時寫volatile的變量呢,相似上方的分析(線程1和線程2同時寫入a的值,或者更多的線程同時寫呢…若是將Java語句拆成字節碼,咱們在Java代碼中的一行代碼,可能獲得多行字節碼,同理一行字節碼可能獲得多行彙編…因此可見性不等於原子性),咱們很容易就能獲得volatile也是不絕對安全的,他不能保證操做的原子性。因此若是想要真正的併發安全(操做原子性),咱們仍是得使用synchornized。

當且僅當知足如下條件時,才應該使用volatile變量:

  • 對變量的寫操做不依賴變量的當前值,或者你能確保只有單個線程更新變量的值。
  • 該變量不會與其餘狀態變量一塊兒歸入不變性條件中。
  • 在訪問變量時不須要加鎖。

推薦volatile變量用法:

做爲標誌位

/**
 * @Author: dhcao
 * @Version: 1.0
 */
public class useVolatile {

    /**
     * flag做爲標誌位,若是發生改變,即要通知全部線程
     */
    volatile boolean flag = false;
    
    public void add(){
        
        while(!flag){
            System.out.println("還不到改變時候...");
        }
    }
}

​ 這只是一個演示,假設咱們有操做:咱們在商城活動中,記錄下100個最早進入的ip地址做爲幸運用戶(不考慮ip重複)!咱們能夠增長一個計數器,當計數器達到100時,將flag修改成true,這樣,就算多個線程同時進來,他們依然能夠知道,已經有100個用戶了。

相關文章
相關標籤/搜索