Java 併發編程(三):如何保證共用變數的可見性?

来源:https://www.cnblogs.com/qing-gee/archive/2019/10/12/11657905.html
-Advertisement-
Play Games

上一篇,我們談了談如何通過同步來保證共用變數的原子性(一個操作或者多個操作要麼全部執行並且執行的過程不會被任何因素打斷,要麼就都不執行),本篇我們來談一談如何保證共用變數的可見性(多個線程訪問同一個變數時,一個線程修改了這個變數的值,其他線程能夠立即看得到修改的值)。 我們使用同步的目的不僅是,不希 ...


上一篇,我們談了談如何通過同步來保證共用變數的原子性(一個操作或者多個操作要麼全部執行並且執行的過程不會被任何因素打斷,要麼就都不執行),本篇我們來談一談如何保證共用變數的可見性(多個線程訪問同一個變數時,一個線程修改了這個變數的值,其他線程能夠立即看得到修改的值)。

我們使用同步的目的不僅是,不希望某個線程在使用對象狀態時,另外一個線程在修改狀態,這樣容易造成混亂;我們還希望某個線程修改了對象狀態後,其他線程能夠看到修改後的狀態——這就涉及到了一個新的名詞:記憶體(可省略)可見性。

要瞭解可見性,我們得先來瞭解一下 Java 記憶體模型。

Java 記憶體模型(Java Memory Model,簡稱 JMM)描述了 Java 程式中各種變數(線程之間的共用變數)的訪問規則,以及在 JVM 中將變數存儲到記憶體→從記憶體中讀取變數的底層細節。

要知道,所有的變數都是存儲在主記憶體中的,每個線程會有自己獨立的工作記憶體,裡面保存了該線程使用到的變數副本(主記憶體中變數的一個拷貝)。見下圖。

 

 

也就是說,線程 1 對共用變數 chenmo 的修改要想被線程 2 及時看到,必須要經過 2 個步驟:

1、把工作記憶體 1 中更新過的共用變數刷新到主記憶體中。
2、將主記憶體中最新的共用變數的值更新到工作記憶體 2 中。

那假如共用變數沒有及時被其他線程看到的話,會發生什麼問題呢?

public class Wanger {
    private static boolean chenmo = false;

    public static void main(String[] args) {
        Thread thread = new Thread(new Runnable() {
            @Override
            public void run() {
                while (!chenmo) {
                }
            }
        });
        thread.start();
        try {
            Thread.sleep(500);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        chenmo = true;

    }

}

這段代碼的本意是:在主線程中創建子線程,然後啟動它,當主線程休眠 500 毫秒後,把共用變數 chenmo 的值修改為 true 的時候,子線程中的 while 迴圈停下來。但運行這段代碼後,程式似乎進入了死迴圈,過了 N 個 500 毫秒,也沒有要停下來的意思。

為什麼會這樣呢?

因為主線程對共用變數 chenmo 的修改沒有及時通知到子線程(子線程在運行的時候,會將 chenmo 變數的值拷貝一份放在自己的工作記憶體當中),當主線程更改了 chenmo 變數的值之後,但是還沒來得及寫入到主存當中,那麼子線程此時就不知道主線程對 chenmo 變數的更改,因此還會一直迴圈下去。

換句話說,就是:普通的共用變數不能保證可見性,因為普通共用變數被修改之後,什麼時候被寫入主記憶體是不確定的,當其他線程去讀取時,此時記憶體中可能還是原來的舊值,因此無法保證可見性。

那怎麼解決這個問題呢?

使用 volatile 關鍵字修飾共用變數 chenmo。

因為 volatile 變數被線程訪問時,會強迫線程從主記憶體中重讀變數的值,而當變數被線程修改時,又會強迫線程將最近的值刷新到主記憶體當中。這樣的話,線程在任何時候總能看到變數的最新值。

我們來使用 volatile 修飾一下共用變數 chenmo。

private static volatile boolean chenmo = false;

再次運行代碼後,程式在一瞬間就結束了,500 毫秒畢竟很短啊。在主線程(main 方法)將 chenmo 修改為 true 後,chenmo 變數的值立即寫入到了主記憶體當中;同時,導致子線程的工作記憶體中緩存變數 chenmo 的副本失效了;當子線程讀取 chenmo 變數時,發現自己的緩存副本無效了,就會去主記憶體讀取最新的值(由 false 變為 true 了),於是 while 迴圈也就停止了。

也就是說,在某種場景下,我們可以使用 volatile 關鍵字來安全地共用變數。這種場景之一就是:狀態真正獨立於程式內地其他內容,比如一個布爾狀態標誌(從 false 到 true,也可以再轉換到 false),用於指示發生了一個重要的一次性事件

至於 volatile 的原理和實現機制,本篇不再深入展開了(小編自己沒搞懂,尷尬而不失禮貌的笑一笑)。

需要再次強調地是:

volatile 變數可以被看作是一種 “程度較輕的 synchronized”;與 synchronized 相比,volatile 變數運行時地開銷比較少,但是它所能實現的功能也僅是 synchronized 的一部分(只能確保可見性,不能確保原子性)。

原子性我們上一篇已經討論過了,增量操作(i++)看上去像一個單獨操作,但實際上它是一個由“讀取-修改-寫入”組成的序列操作,因此 volatile 並不能為其提供必須的原子特性。

除了 volatile 和 synchronized,Lock 也能夠保證可見性,它能保證同一時刻只有一個線程獲取鎖然後執行同步代碼,並且在釋放鎖之前會將對變數的修改刷新到主存當中。關於 Lock 的更多細節,我們後面再進行討論。

好了,共用變數的可見性就先介紹到這。希望本篇文章能夠對大家有所幫助,謝謝大家的閱讀。

05、最後

謝謝大家的閱讀,原創不易,喜歡就點個贊,這將是我最強的寫作動力。如果你覺得文章對你有所幫助,也蠻有趣的,就關註一下「沉默王二」公眾號。


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

-Advertisement-
Play Games
更多相關文章
  • 為什麼要用css動畫替換js動畫 導致JavaScript效率低的兩大原因:操作DOM和使用頁面動畫。 用CSS3動畫替代JS模擬動畫的好處: 不占用JS主線程; 可以利用硬體加速; 瀏覽器可對動畫做優化(元素不可見時不動畫減少對FPS影響) CSS3動畫提供了2D和3D以及常規動畫屬性介面,它可以 ...
  • 1、安裝、構建 2、項目目錄 3、antd 修改 src/App.css,在文件頂部引入 antd/dist/antd.css。 antd 目前的預設文案是英文,如果需要使用其他語言,可以參考下麵的方案。 antd 提供了一個 React 組件 ConfigProvider 用於全局配置國際化文案。 ...
  • 之前講解了什麼是微服務:微服務的核心在於服務治理,微服務架構是將複雜臃腫的單體應用進行細粒度的服務化拆分,每個拆分出來的服務各自獨立打包部署,並交由小團隊進行開發和運維,從而極大地提高了應用交付的效率。 什麼時候進行服務化拆分?拆分單體應用有哪些標準呢? 什麼時候進行服務化拆分? 比如做社交 App ...
  • 1 面向對象簡述 將 {1,3,45,56,78,90}轉化為[1,3,45,56,78,90] 1 2 方法1:面向過程 public class Student { int age = 13; String name = "wangsiyu"; public void study(){ Syst ...
  • 一、Django的內置分頁器(paginator) view index.html: 擴展 show.html model.py文件內容: 二、自定義分頁 當資料庫中數據有很多,我們通常會在前端頁面做分頁展示。 分頁的數據可以在前端頁面實現,也可以在後端實現分頁。 後端實現分頁的原理就是每次只請求一 ...
  • Tair是為瞭解決什麼問題而生? Redis很好用,相比memcached多了很多數據結構,支持持久化。但是在很長一段時間里,原生是不支持分散式的。後來就出現了很多redis集群類產品,Tair是其中勝出的優秀作品之一。 所以Tair的特性都是一些集群的特性,比如:容錯、解決單點故障、跨機房管理、多 ...
  • 前言 今天我們一起看看這個觀察者模式,這個模式簡單來說就是一個發佈訂閱類似的模式。按照名字來理解也就是存在一個觀察者和一個被觀察者。說幾個例子給大家聽,大家應該就明白了。例如在我們現在通過銀行卡支付之後,會收到銀行發過來的提示信息。例如當我們話費餘額或者流量不足之時也會收到提示信息。這其中的邏輯幫我 ...
  • 解釋器模式(Interpreter): 從名稱上來看看這個模式,個人的最初理解“解釋器”和Google的中英翻譯功能類似。如果有一天你去國外旅游去了,比如去美國吧,美國人是講英語的,我們是講漢語的,如果英語聽不懂,講不好,估計溝通就完蛋了,不能溝通,估計玩的就很難盡興了,因為有很多景點的解說你可能不 ...
一周排行
    -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.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...