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
  • 示例項目結構 在 Visual Studio 中創建一個 WinForms 應用程式後,項目結構如下所示: MyWinFormsApp/ │ ├───Properties/ │ └───Settings.settings │ ├───bin/ │ ├───Debug/ │ └───Release/ ...
  • [STAThread] 特性用於需要與 COM 組件交互的應用程式,尤其是依賴單線程模型(如 Windows Forms 應用程式)的組件。在 STA 模式下,線程擁有自己的消息迴圈,這對於處理用戶界面和某些 COM 組件是必要的。 [STAThread] static void Main(stri ...
  • 在WinForm中使用全局異常捕獲處理 在WinForm應用程式中,全局異常捕獲是確保程式穩定性的關鍵。通過在Program類的Main方法中設置全局異常處理,可以有效地捕獲並處理未預見的異常,從而避免程式崩潰。 註冊全局異常事件 [STAThread] static void Main() { / ...
  • 前言 給大家推薦一款開源的 Winform 控制項庫,可以幫助我們開發更加美觀、漂亮的 WinForm 界面。 項目介紹 SunnyUI.NET 是一個基於 .NET Framework 4.0+、.NET 6、.NET 7 和 .NET 8 的 WinForm 開源控制項庫,同時也提供了工具類庫、擴展 ...
  • 說明 該文章是屬於OverallAuth2.0系列文章,每周更新一篇該系列文章(從0到1完成系統開發)。 該系統文章,我會儘量說的非常詳細,做到不管新手、老手都能看懂。 說明:OverallAuth2.0 是一個簡單、易懂、功能強大的許可權+可視化流程管理系統。 有興趣的朋友,請關註我吧(*^▽^*) ...
  • 一、下載安裝 1.下載git 必須先下載並安裝git,再TortoiseGit下載安裝 git安裝參考教程:https://blog.csdn.net/mukes/article/details/115693833 2.TortoiseGit下載與安裝 TortoiseGit,Git客戶端,32/6 ...
  • 前言 在項目開發過程中,理解數據結構和演算法如同掌握蓋房子的秘訣。演算法不僅能幫助我們編寫高效、優質的代碼,還能解決項目中遇到的各種難題。 給大家推薦一個支持C#的開源免費、新手友好的數據結構與演算法入門教程:Hello演算法。 項目介紹 《Hello Algo》是一本開源免費、新手友好的數據結構與演算法入門 ...
  • 1.生成單個Proto.bat內容 @rem Copyright 2016, Google Inc. @rem All rights reserved. @rem @rem Redistribution and use in source and binary forms, with or with ...
  • 一:背景 1. 講故事 前段時間有位朋友找到我,說他的窗體程式在客戶這邊出現了卡死,讓我幫忙看下怎麼回事?dump也生成了,既然有dump了那就上 windbg 分析吧。 二:WinDbg 分析 1. 為什麼會卡死 窗體程式的卡死,入口門檻很低,後續往下分析就不一定了,不管怎麼說先用 !clrsta ...
  • 前言 人工智慧時代,人臉識別技術已成為安全驗證、身份識別和用戶交互的關鍵工具。 給大家推薦一款.NET 開源提供了強大的人臉識別 API,工具不僅易於集成,還具備高效處理能力。 本文將介紹一款如何利用這些API,為我們的項目添加智能識別的亮點。 項目介紹 GitHub 上擁有 1.2k 星標的 C# ...