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
  • 前言 本文介紹一款使用 C# 與 WPF 開發的音頻播放器,其界面簡潔大方,操作體驗流暢。該播放器支持多種音頻格式(如 MP4、WMA、OGG、FLAC 等),並具備標記、實時歌詞顯示等功能。 另外,還支持換膚及多語言(中英文)切換。核心音頻處理採用 FFmpeg 組件,獲得了廣泛認可,目前 Git ...
  • OAuth2.0授權驗證-gitee授權碼模式 本文主要介紹如何筆者自己是如何使用gitee提供的OAuth2.0協議完成授權驗證並登錄到自己的系統,完整模式如圖 1、創建應用 打開gitee個人中心->第三方應用->創建應用 創建應用後在我的應用界面,查看已創建應用的Client ID和Clien ...
  • 解決了這個問題:《winForm下,fastReport.net 從.net framework 升級到.net5遇到的錯誤“Operation is not supported on this platform.”》 本文內容轉載自:https://www.fcnsoft.com/Home/Sho ...
  • 國內文章 WPF 從裸 Win 32 的 WM_Pointer 消息獲取觸摸點繪製筆跡 https://www.cnblogs.com/lindexi/p/18390983 本文將告訴大家如何在 WPF 裡面,接收裸 Win 32 的 WM_Pointer 消息,從消息裡面獲取觸摸點信息,使用觸摸點 ...
  • 前言 給大家推薦一個專為新零售快消行業打造了一套高效的進銷存管理系統。 系統不僅具備強大的庫存管理功能,還集成了高性能的輕量級 POS 解決方案,確保頁面載入速度極快,提供良好的用戶體驗。 項目介紹 Dorisoy.POS 是一款基於 .NET 7 和 Angular 4 開發的新零售快消進銷存管理 ...
  • ABP CLI常用的代碼分享 一、確保環境配置正確 安裝.NET CLI: ABP CLI是基於.NET Core或.NET 5/6/7等更高版本構建的,因此首先需要在你的開發環境中安裝.NET CLI。這可以通過訪問Microsoft官網下載並安裝相應版本的.NET SDK來實現。 安裝ABP ...
  • 問題 問題是這樣的:第三方的webapi,需要先調用登陸介面獲取Cookie,訪問其它介面時攜帶Cookie信息。 但使用HttpClient類調用登陸介面,返回的Headers中沒有找到Cookie信息。 分析 首先,使用Postman測試該登陸介面,正常返回Cookie信息,說明是HttpCli ...
  • 國內文章 關於.NET在中國為什麼工資低的分析 https://www.cnblogs.com/thinkingmore/p/18406244 .NET在中國開發者的薪資偏低,主要因市場需求、技術棧選擇和企業文化等因素所致。歷史上,.NET曾因微軟的閉源策略發展受限,儘管後來推出了跨平臺的.NET ...
  • 在WPF開發應用中,動畫不僅可以引起用戶的註意與興趣,而且還使軟體更加便於使用。前面幾篇文章講解了畫筆(Brush),形狀(Shape),幾何圖形(Geometry),變換(Transform)等相關內容,今天繼續講解動畫相關內容和知識點,僅供學習分享使用,如有不足之處,還請指正。 ...
  • 什麼是委托? 委托可以說是把一個方法代入另一個方法執行,相當於指向函數的指針;事件就相當於保存委托的數組; 1.實例化委托的方式: 方式1:通過new創建實例: public delegate void ShowDelegate(); 或者 public delegate string ShowDe ...