深入分析Volatile的實現原理
本文屬於作者原創,原文發表於InfoQ:http://www.infoq.com/cn/articles/ftf-java-volatile
引言
在多執行緒併發程式設計中synchronized和Volatile都扮演著重要的角色,Volatile是輕量級的synchronized,它在多處理器開發中保證了共享變數的“可見性”。可見性的意思是當一個執行緒修改一個共享變數時,另外一個執行緒能讀到這個修改的值。它在某些情況下比synchronized的開銷更小,本文將深入分析在硬體層面上Inter處理器是如何實現Volatile的,通過深入分析能幫助我們正確的使用Volatile變數。
術語定義
術語 | 英文單詞 | 描述 |
共享變數 | 在多個執行緒之間能夠被共享的變數被稱為共享變數。共享變數包括所有的例項變數,靜態變數和陣列元素。他們都被存放在堆記憶體中,Volatile只作用於共享變數。 | |
記憶體屏障 | Memory Barriers | 是一組處理器指令,用於實現對記憶體操作的順序限制。 |
快取行 | Cache line | 快取中可以分配的最小儲存單位。處理器填寫快取線時會載入整個快取線,需要使用多個主記憶體讀週期。 |
原子操作 | Atomic operations | 不可中斷的一個或一系列操作。 |
快取行填充 | cache line fill | 當處理器識別到從記憶體中讀取運算元是可快取的,處理器讀取整個快取行到適當的快取(L1,L2,L3的或所有) |
快取命中 | cache hit | 如果進行快取記憶體行填充操作的記憶體位置仍然是下次處理器訪問的地址時,處理器從快取中讀取運算元,而不是從記憶體。 |
寫命中 | write hit | 當處理器將運算元寫回到一個記憶體快取的區域時,它首先會檢查這個快取的記憶體地址是否在快取行中,如果存在一個有效的快取行,則處理器將這個運算元寫回到快取,而不是寫回到記憶體,這個操作被稱為寫命中。 |
寫缺失 | write misses the cache | 一個有效的快取行被寫入到不存在的記憶體區域。 |
Volatile的官方定義
Java語言規範第三版中對volatile的定義如下: java程式語言允許執行緒訪問共享變數,為了確保共享變數能被準確和一致的更新,執行緒應該確保通過排他鎖單獨獲得這個變數。Java語言提供了volatile,在某些情況下比鎖更加方便。如果一個欄位被宣告成volatile,java執行緒記憶體模型確保所有執行緒看到這個變數的值是一致的。
為什麼要使用Volatile
Volatile變數修飾符如果使用恰當的話,它比synchronized的使用和執行成本會更低,因為它不會引起執行緒上下文的切換和排程。
Volatile的實現原理
那麼Volatile是如何來保證可見性的呢?在x86處理器下通過工具獲取JIT編譯器生成的彙編指令來看看對Volatile進行寫操作CPU會做什麼事情。
Java程式碼: | instance = new Singleton();//instance是volatile變數 |
彙編程式碼: | 0x01a3de1d: movb $0x0,0x1104800(%esi); 0x01a3de24: lock addl $0x0,(%esp); |
有volatile變數修飾的共享變數進行寫操作的時候會多第二行彙編程式碼,通過查IA-32架構軟體開發者手冊可知,lock字首的指令在多核處理器下會引發了兩件事情。
- 將當前處理器快取行的資料會寫回到系統記憶體。
- 這個寫回記憶體的操作會引起在其他CPU裡快取了該記憶體地址的資料無效。
這兩件事情在IA-32軟體開發者架構手冊的第三冊的多處理器管理章節(第八章)中有詳細闡述。
Lock字首指令會引起處理器快取回寫到記憶體。Lock字首指令導致在執行指令期間,聲言處理器的 LOCK# 訊號。在多處理器環境中,LOCK# 訊號確保在聲言該訊號期間,處理器可以獨佔使用任何共享記憶體。(因為它會鎖住匯流排,導致其他CPU不能訪問匯流排,不能訪問匯流排就意味著不能訪問系統記憶體),但是在最近的處理器裡,LOCK#訊號一般不鎖匯流排,而是鎖快取,畢竟鎖匯流排開銷比較大。在8.1.4章節有詳細說明鎖定操作對處理器快取的影響,對於Intel486和Pentium處理器,在鎖操作時,總是在匯流排上聲言LOCK#訊號。但在P6和最近的處理器中,如果訪問的記憶體區域已經快取在處理器內部,則不會聲言LOCK#訊號。相反地,它會鎖定這塊記憶體區域的快取並回寫到記憶體,並使用快取一致性機制來確保修改的原子性,此操作被稱為“快取鎖定”,快取一致性機制會阻止同時修改被兩個以上處理器快取的記憶體區域資料。
一個處理器的快取回寫到記憶體會導致其他處理器的快取無效。IA-32處理器和Intel 64處理器使用MESI(修改,獨佔,共享,無效)控制協議去維護內部快取和其他處理器快取的一致性。在多核處理器系統中進行操作的時候,IA-32 和Intel 64處理器能嗅探其他處理器訪問系統記憶體和它們的內部快取。它們使用嗅探技術保證它的內部快取,系統記憶體和其他處理器的快取的資料在匯流排上保持一致。例如在Pentium和P6 family處理器中,如果通過嗅探一個處理器來檢測其他處理器打算寫記憶體地址,而這個地址當前處理共享狀態,那麼正在嗅探的處理器將無效它的快取行,在下次訪問相同記憶體地址時,強制執行快取行填充。----------------------------------------------------------------- 分割線 ----------------------------------------------------------------------
總結:
volatile是輕量級的synchronized,他們都在併發程式設計中扮演著重要的角色。volatile保證了共享變數的“可見性”。通常,volatile的使用成本比synchronized更小,因為使用volatile不會引起執行緒的上下文切換。
共享變數的可見性如何保證?
使用volatile修飾的屬性,在生成的彙編指令中,會有一個lock字首,lock字首在多核處理器下會保證兩件事:1.將當前CPU的快取行資料回寫到系統記憶體中。在回寫記憶體時,會使用快取鎖定操作;2.這個回寫記憶體操作會引起其它CPU快取中該資料無效,主要通過快取一致性機制保證。
相關文章
- 【死磕Java併發】—–深入分析volatile的實現原理Java
- 併發——深入分析ThreadLocal的實現原理thread
- Volatile的實現原理(看這篇就夠了)
- 【死磕Java併發】-----深入分析synchronized的實現原理Javasynchronized
- 詳解鎖原理,synchronized、volatile+cas底層實現synchronized
- 就是要你懂Java中volatile關鍵字實現原理Java
- Volatile的底層原理
- 基礎篇:詳解鎖原理,volatile+cas、synchronized的底層實現synchronized
- 深入分析HTTP代理的原理HTTP
- Java多執行緒(一)之volatile深入分析Java執行緒
- volatile關鍵字的作用、原理
- 深入分析 Docker 映象原理Docker
- Java 併發機制底層實現 —— volatile 原理、synchronize 鎖優化機制Java優化
- 深入分析 Javac 編譯原理Java編譯原理
- volatile底層原理詳解
- 淺析volatile原理及其使用
- synchronized 的實現原理synchronized
- Category的實現原理Go
- nio的實現原理
- sysbench的實現原理
- HashMap的實現原理HashMap
- Vitepress 的實現原理Vite
- 對Koa-middleware實現機制的深入分析
- 從根源上解析 Java volatile 關鍵字的實現Java
- Hollis原創|深入分析Java的編譯原理Java編譯原理
- 前端路由的實現原理前端路由
- ACID的實現原理
- 堆的原理與實現
- LinkedList 的實現原理
- Mobx 思想的實現原理
- ConcurrentHashMap的實現原理HashMap
- (iOS)KVO 的實現原理iOS
- PHPsession的實現原理PHPSession
- Lucene字典的實現原理
- Docker的核心實現原理Docker
- React Router 的實現原理React
- 深入分析Java中的PriorityQueue底層實現與原始碼Java原始碼
- 【面試普通人VS高手系列】volatile關鍵字有什麼用?它的實現原理是什麼?面試