volatile實現原理

来源:https://www.cnblogs.com/sadamu0912/archive/2018/12/03/volatile.html
-Advertisement-
Play Games

首先併發編程有三大特性: 可見性,有序性,原子性。volatile關鍵字實現了前面兩個特性。那麼它是如何實現這兩個特性的呢? 首先是可見性。可見性主要是讓緩存,直接寫穿透到主存中。然後另外的cpu 通過底層的硬體層面的嗅探,可以發現自己cpu本地的緩存已經失效。然後到主存中直接讀取。現在讓我們來看看 ...


  首先併發編程有三大特性: 可見性,有序性,原子性。volatile關鍵字實現了前面兩個特性。那麼它是如何實現這兩個特性的呢?

  首先是可見性。可見性主要是讓緩存,直接寫穿透到主存中。然後另外的cpu 通過底層的硬體層面的嗅探,可以發現自己cpu本地的緩存已經失效。然後到主存中直接讀取。現在讓我們來看看,cpu裡面的緩存具體和主存如何交互。

  說道這裡首先需要瞭解,電腦內部存儲器結構。每個核中都有自己的通用寄存器,比如eax,ebx,edx,esi,esp.訪問這些寄存器裡面的內容,只要一個機器周期,就夠了,通常小於1ns..然後是L1,L2的本地core的緩存。 通常在10個機器周期左右。大約10ns. L3級緩存是多core共用的。

 

 以我們常見的X86晶元為例,Cache的結構下圖所示:整個Cache被分為S個組,每個組是又由E行個最小的存儲單元——Cache Line所組成,而一個Cache Line中有B(B=64)個位元組用來存儲數據,即每個Cache Line能存儲64個位元組的數據,每個Cache Line又額外包含一個有效位(valid bit)、t個標記位(tag bit),其中valid bit用來表示該緩存行是否有效;tag bit用來協助定址,唯一標識存儲在CacheLine中的塊;而Cache Line里的64個位元組其實是對應記憶體地址中的數據拷貝。根據Cache的結構題,我們可以推算出每一級Cache的大小為B×E×S。

 

1級大概是32k 或者32K X2  ,2級大概是 256K或者256KX2 ,L3一般是3M左右。當多線程併發訪問一段代碼的時候,讀取變數到本地的core進行計算,然後把數據寫入到緩存中,假如沒有volatile關鍵字的話,緩存採用的是write back 策略,直接寫到緩存,看如下代碼,雖然啟用了10個線程進行計數,但是列印出來的count值是0.即使sleep(100),100ms,等所有線程都起來了,也是得到的結果都是不定的,因為無法確定緩存什麼時候,換出寫到主存中。

 1 public class VolatileTest {
 2 
 3     private int count ;
 4     public void increase() {
 5         count++;
 6     }
 7     public void  getCount(){
 8         System.out.println(count);
 9     }
10     public static void main(String[] args) throws InterruptedException{
11         VolatileTest test =  new VolatileTest();
12         for(int i=0;i<10;i++){
13             new Thread(){
14                 @Override
15                 public void run() {
16                     for(int j=0;j<1000;j++)
17                         test.increase();
18                 }
19             }.start();
20         }
21         Thread.sleep(100);
22         test.getCount();
23     }
24 }

volatile 作用1 就是一個線程改變了共用變數的值,其它線程馬上能看見,就是可見性。比如下麵的這段代碼。

 1 import java.util.concurrent.CountDownLatch;
 2 
 3 public class VolatileTest2 {
 4 
 5     private static volatile boolean status=false ;
 6 
 7     private static CountDownLatch start  = new CountDownLatch(1);
 8 
 9     public void setStatusTrue(){
10         status =true;
11     }
12     public void  getStatus(){
13         System.out.println(status);
14     }
15     public static void main(String[] args) throws InterruptedException{
16         VolatileTest2 test =  new VolatileTest2();
17         new Thread(new Task2(start,test)).start();
18         for(int i=0;i<10;i++){
19             new Thread(new Task1(start,test)).start();
20         }
21     }
22 }
23 
24 class Task1 implements Runnable{
25     private CountDownLatch latch;
26     private VolatileTest2 test ;
27     public Task1(CountDownLatch start,VolatileTest2 test){
28         this.latch = start;
29         this.test = test;
30     }
31     @Override
32     public void run() {
33         try{
34            latch.await();
35         }catch (Exception e){
36         }
37             test.getStatus();
38     }
39 }
40 
41 /**
42  * 這個線程吧狀態設置成true,然後同步計數器馬上變成0.之後,就其它線程馬上就能看到status狀態為true
43  */
44 class Task2 implements Runnable{
45     private CountDownLatch latch;
46     private VolatileTest2 test ;
47     public Task2(CountDownLatch start,VolatileTest2 test){
48         this.latch = start;
49         this.test = test;
50     }
51     @Override
52     public void run() {
53         test.setStatusTrue();
54         latch.countDown();
55         System.out.println("countDown===");
56     }
57 }

具體的原理,這裡涉及到緩存一致性原理,MESI 協議

失效(Invalid)緩存段,要麼已經不在緩存中,要麼它的內容已經過時。為了達到緩存的目的,這種狀態的段將會被忽略。一旦緩存段被標記為失效,那效果就等同於它從來沒被載入到緩存中。

共用(Shared)緩存段,它是和主記憶體內容保持一致的一份拷貝,在這種狀態下的緩存段只能被讀取,不能被寫入。多組緩存可以同時擁有針對同一記憶體地址的共用緩存段,這就是名稱的由來。

獨占(Exclusive)緩存段,和S狀態一樣,也是和主記憶體內容保持一致的一份拷貝。區別在於,如果一個處理器持有了某個E狀態的緩存段,那其他處理器就不能同時持有它,所以叫“獨占”。這意味著,如果其他處理器原本也持有同一緩存段,那麼它會馬上變成“失效”狀態。

已修改(Modified)緩存段,屬於臟段,它們已經被所屬的處理器修改了。如果一個段處於已修改狀態,那麼它在其他處理器緩存中的拷貝馬上會變成失效狀態,這個規律和E狀態一樣。此外,已修改緩存段如果被丟棄或標記為失效,那麼先要把它的內容回寫到記憶體中——這和回寫模式下常規的臟段處理方式一樣。

在寫入時鎖定緩存,稱為Exclusive狀態,然後同時寫入緩存和主存,當讀取數據的時候,強行,從主存中讀取,並且申請緩存行填充。

 

2 有序性,這個又如何保證呢?

《深入理解Java虛擬機》中有這句話“”“觀察加入volatile關鍵字和沒有加入volatile關鍵字時所生成的彙編代碼發現,加入volatile關鍵字時,會多出一個lock首碼指令”“”,lock首碼指令實際上相當於一個記憶體屏障(也成記憶體柵欄)它確保指令重排序時不會把其後面的指令排到記憶體屏障之前的位置,也不會把前面的指令排到記憶體屏障的後面;即在執行到記憶體屏障這句指令時,在它前面的操作已經全部完成;至於什麼是記憶體屏障,不做深入瞭解。只需要知道是CPU Out-of-order execution 和 compiler reordering optimizations。用於對記憶體操作的順序限制。

 

Memory access instructions, such as loads and stores, typically take longer to execute than other instructions. Therefore, compilers use registers to hold frequently used values and processors use high speed caches to hold the most frequently used memory locations. Another common optimization is for compilers and processors to rearrange the order that instructions are executed so that the processor does not have to wait for memory accesses to complete. This can result in memory being accessed in a different order than specified in the source code. While this typically will not cause a problem in a single thread of execution, it can cause a problem if the location can also be accessed from another processor or device.

 


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

-Advertisement-
Play Games
更多相關文章
  • nodejs 使用 js 模塊 Intro 最近需要用 nodejs 做一個爬蟲,Google 有一個 Puppeteer 的項目,可以用它來做爬蟲,有關 Puppeteer 的介紹網上也有很多,在這裡就不做詳細介紹了。 node 小白,開始的時候有點懵逼,模塊導出也不會。 官方文檔上說支持 .mj ...
  • 1.Js操作css樣式 div.style.width=”100px”.在div標簽內我們添加了一個style屬性,並設定 了width值。這種寫法會給標簽帶來大量的style屬性,跟實際項目是不符。 我們沒有讓css和html分離。 所以如果是為了獲取css樣式 window.getCompute ...
  • 聲明 本系列文章內容全部梳理自以下幾個來源: 《JavaScript權威指南》 "MDN web docs" "Github:smyhvae/web" "Github:goddyZhao/Translation/JavaScript" 作為一個前端小白,入門跟著這幾個來源學習,感謝作者的分享,在其基 ...
  • 即時通訊:支持好友,群組,發圖片、文件,消息聲音提醒,離線消息,保留聊天記錄 (即時聊天功能支持手機端,詳情下麵有截圖) 工作流模塊 1.模型管理 :web線上流程設計器、預覽流程xml、導出xml、部署流程 2.流程管理 :導入導出流程資源文件、查看流程圖、根據流程實例反射出流程模型、激活掛起 3 ...
  • 訪問者模式(Visitor Pattern)的目的是封裝一些於某種數據結構元素之上的操作,一旦這些元素需要修改,接受這個操作的數據結構則可以保持不變。 定義: 封裝一些作用於某種數據結構中的各元素的操作,它可以在不改變數據結構的前提下定義於作用這些元素的新的操作。 訪問者模式的類圖如下。 訪問者模式 ...
  • 備忘錄模式(Memento Pattern)又稱為快照(Snapshot)模式或Token模式。 意思是:在不破壞封裝性的前提下,捕獲一個對象的內部狀態,併在該對象之外保存這個對象。這樣,以後就可以將該對象恢復到原先保存的狀態。 通俗地說,備忘錄模式就是將一個對象進行備份,提供一種程式數據的備份方法 ...
  • 外觀模式又稱為門面模式Facade是一種簡單的設計模式,但是他背後的思想為迪米特原則,理解門面模式更有助於理解迪米特原則--不要和陌生人說話的原則,可以降低系統的耦合程度,本文介紹了外觀模式的意圖,結構,並且給出了java代碼示例。 ...
  • 1、編寫裝飾器,為多個函數加上認證的功能(用戶的賬號密碼來源於文件)要求登錄成功一次,後續的函數都無需再輸入用戶名和密碼 2、編寫裝飾器,為多個函數加上記錄調用功能,要求每次調用函數都將被調用的函數名稱寫入文件 進階練習:1.編寫下載網頁內容的函數,要求功能是:用戶傳入一個url,函數返回下載頁面的 ...
一周排行
    -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.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...