理解volatile

来源:http://www.cnblogs.com/ASPNET2008/archive/2017/02/12/6391429.html
-Advertisement-
Play Games

理解volatile 平時工作中對於多線程的應用並不太多,但是不能說工作中不應用就可以對此不去瞭解,至少要做的知道有這麼個東西,主要是作什麼的,這樣有助於看其它人寫的代碼。提到這個volatile,一般都會想到併發,同步,鎖之類,但要想搞清楚需要看看下麵一些知識。 處理器,高速緩存,主記憶體之間的關係 ...


理解volatile

平時工作中對於多線程的應用並不太多,但是不能說工作中不應用就可以對此不去瞭解,至少要做的知道有這麼個東西,主要是作什麼的,這樣有助於看其它人寫的代碼。提到這個volatile,一般都會想到併發,同步,鎖之類,但要想搞清楚需要看看下麵一些知識。

處理器,高速緩存,主記憶體之間的關係

高速緩存的作用是什麼?
由於處理器與主記憶體在處理數據的速度上有數量級的差異,所以引入了比主記憶體速度更快的高速緩存。處理器從主記憶體中讀取數據放到高速緩存中做交互運算,最後回寫到主記憶體中。
引入高速緩存會帶來哪些問題?

  • 使電腦系統更加複雜,但相對帶來的優點還是值得的。
  • 緩存一致性問題
    多個處理器如果操作的是同一個主記憶體中的變數,那麼就會出現以誰為準的問題。這就要靠一些規定的協議來維護。

JAVA線程,工作記憶體,主記憶體之間的關係

這是JAVA記憶體模型範疇,主要是用來屏蔽硬體與系統的記憶體訪問差異,讓JAVA程式可以在不同的平臺上達到相同的記憶體訪問效果。

這裡說的工作記憶體,主記憶體與JVM記憶體中講的JAVA堆,棧,方法區不是同一層次上的概念,需要區分。

記憶體交互操作

主要是工作記憶體與主記憶體之間的具體交互協議,即一個變數是如何從主記憶體載入到工作記憶體,然後從工作記憶體如何同步到主記憶體的具體實現細節,總共有以下幾個操作:

  • lock,標識一個變數被某個變更獨占
  • write,將工作記憶體中的變數回寫到主記憶體中。這點是volatile的關鍵,它能夠保證被標記了volatile的變數一旦被修改馬上執行回寫主記憶體的操作,從而保證其它線程的可見性。

原子性,可見性,有序性

這三個特性是併發操作中需要處理的問題,volatile與下麵兩個特性有關聯。所以在符合可見性以及有序性特性的場景就是volatile的適用場景,也是它的作用所在。

  • 原子性
    上面提到的記憶體交互操作中除了兩個鎖相關的都可以認為是原子性操作。
  • 可見性
    意思是說對於某個共用的變數,一個線程對其做了修改之後其它線程立馬可見。volatile在可見性方面上相對普通的變數有著重大區別,它能夠確保共用變數被修改後馬上執行回寫主記憶體的操作,而普通的變數做不到。
  • 有序性
    這裡有一個有意思的東西就是重排序,它的大體意思就是編寫的代碼順序不一定就是最終被處理器執行的順序,這是為了處理器內部的運算單元能夠儘量的被充分利用,有興趣的可以仔細研究下。而使用了volatile的變數能夠確保不被重排序,這是與普通變數不同的第二個重要區別。

volatile的適用場景

  • 運算的結果不依賴共用變數當前的值。

反例,多線程對volatile靜態變數執行累加。
這裡的count++看起來是一個語句,但對應的位元組碼不是一條,在執行多條位元組碼的期間主記憶體中的值有可能被其它線程所修改,從而導致回寫到記憶體中的值不一致。下麵代碼的輸出並不總是正確。

private static volatile int count=0;

private static void increase(){
    count++;
}

public static void main(String[] args) throws Exception {
    Thread[] threads=new Thread[30];
    for(int i=0;i<threads.length;i++){
        threads[i]=new Thread(new Runnable() {
            @Override
            public void run() {
                for(int k=0;k<10000;k++){
                    increase();
                    System.out.println(count);
                }
            }
        });
        threads[i].start();
        Thread.sleep(1);
    }
}

下麵是count++對應的位元組碼,很明顯是多條位元組碼。volatile只能保證每次讀取變數的值是最新的,它在獲取到主記憶體變數後是對其副本進行修改,並不會鎖定主記憶體中的值。

 static void access$000();
    flags: ACC_STATIC, ACC_SYNTHETIC
    Code:
      stack=0, locals=0, args_size=0
         0: invokestatic  #2                  // Method increase:()V
         3: return        
      LineNumberTable:
        line 6: 0

  static int access$100();
    flags: ACC_STATIC, ACC_SYNTHETIC
    Code:
      stack=1, locals=0, args_size=0
         0: getstatic     #1                  // Field count:I
         3: ireturn       
      LineNumberTable:
        line 6: 0

  static {};
    flags: ACC_STATIC
    Code:
      stack=1, locals=0, args_size=0
         0: iconst_0      
         1: putstatic     #1                  // Field count:I
         4: return        
      LineNumberTable:
        line 8: 0
}

某些共用的狀態變數是非常適合的,比如dubbo提供的accesslog filter。某些資源只載入一次的場景特別適用,比如應用程式的配置文件的載入,變數的初始化之類。

private volatile ScheduledFuture<?> logFuture = null;

private void init() {
    if (logFuture == null) {
        synchronized (logScheduled) {
            if (logFuture == null) {
                logFuture = ...;
            }
        }
    }
}
  • 也不需要與其它的變數共同參與不變約束

volatile變數與普通的變數在執行性能上的區別

由於volatile需要在修改變數時增加記憶體屏障語句,理所當然的相對沒有記憶體屏障語句的普通變數要慢一些。

volatile與同步語法塊,鎖的區別

volatile的特點是當線程對變數修改後馬上回與記憶體保證可見性,同時禁止重排序保證程式執行的有序性。由於它操作的是副本並不會對主記憶體加鎖,所以並不具體同步語法塊以及鎖的特點即可一時刻同一變數只允許一個線程操作。

引用

本文主要引用《深入理解JAVA虛似機》


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • .NET Core針對緩存提供了很好的支持 ,我們不僅可以選擇將數據緩存在應用進程自身的記憶體中,還可以採用分散式的形式將緩存數據存儲在一個“中心資料庫”中。對於分散式緩存,.NET Core提供了針對Redis和SQL Server的原生支持。除了這個獨立的緩存系統之外,ASP.NET Core還借... ...
  • ZXing .NET 的項目主頁https://zxingnet.codeplex.com/ 代碼基本上抄襲自下麵兩篇文章 XDhttp://www.cnblogs.com/tianma3798/p/5426869.htmlhttp://www.cnblogs.com/tianma3798/p/54 ...
  • 20170210問題解析請點擊今日問題下方的“【Java每日一題】20170213”查看(問題解析在公眾號首發,公眾號ID:weknow619) 今日問題: 請問主程式輸出結果是什麼?(點擊以下“【Java每日一題】20170213”查看20170210問題解析) 題目原發佈於公眾號、簡書:【Jav ...
  • Java中的內部類 小記:內部類可以作為其他類的成員,而且可訪問它所在類的成員。 ...
  • 最近開始看Spring Boot,發現其開發起來真是方便。今天就來實現一個簡單的Spring MVC 請求,純Java代碼的哦。 1、Maven必不可少,先看看都載入了那些依賴: 2、Controller 3、頁面,放在src/main/resources/templates/目錄下,方便解析 4、 ...
  • 浮點數會有精度損失這個在上大學的時候就已經被告知,但是至今完全沒有想明白其中的原由,老師講的時候也是一筆帶過的,自己也沒有好好琢磨。終於在工作的時候碰到了,於是google了一番。 問題: 對兩個double類型的值進行運算,有時會出現結果值異常的問題。比如: 輸出: 39.989999999999 ...
  • 最近一直在搞郵件這塊,本來我們郵件發送是用的騰訊免費的企業郵箱,郵件功能沒有問題,但是由於郵件的限制,如下: 這些限制導致我們的部分客戶是收不到郵件的,哪怕付費,這樣的固定頻率限制也是無法解決的,可以說我們國內的郵件廠商都是這樣,而國外的卻要收費。 那麼問題來了,如何突破發送郵件的頻率限制? 1. ...
  • 當JSTL標簽庫已經無法滿足我們的需求時候,就需要自己開發自定義標簽,來滿足我們的需求,自定義標簽實際上是一個普通的java類,繼承SimpleTagSupport類。 做類。派生自SimpleTagSupport,重寫doTag()方法。getJspBody(),getJspContext(),i ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...