Volatile的實現原理(看這篇就夠了)
談到併發程式設計就不得不提到併發三要素:原子性、可見性、有序性,而Volatile就會涉及到可見性與有序性,可見Volatile在併發程式設計的重要的地位。
所以需要重點掌握Volatile,為了助大家掌握好Volatile,我會重點講到以下5點:
1.Volatile關鍵字
2.Java記憶體模型
3.Volatile記憶體模型可見性
4.Volatile的工作原理
5.Volatile的原始碼案例
在談Volatile之前,我們先回顧下Java記憶體模型的三要素:原子性、可見性、有序性,也就是大家常提到的併發程式設計三要素。
併發程式設計的三要素
1.原子性
和資料庫事務中的原子性一樣,滿足原子性特性的操作是不可中斷的,要麼全部執行成功要麼全部執行失敗
只有簡單的讀取、賦值(而且必須是將數字賦值給某個變數,變數之間的相互賦值不是原子操作)才是原子操作。
比如:i = 2;j = i;i++;i = i + 1;
上面4個操作中,i=2是讀取操作,必定是原子性操作,j=i你以為是原子性操作,其實吧,分為兩步,一是讀取i的值,然後再賦值給j,這就是2步操作了,稱不上原子操作,i++和i = i + 1其實是等效的,讀取i的值,加1,再寫回主存,那就是3步操作了。
所以上面的舉例中,最後的值可能出現多種情況,就是因為滿足不了原子性。
非原子操作都會存線上程安全問題,需要我們使用同步技術(sychronized)來讓它變成一個原子操作,java的concurrent包下提供了一些原子類:比如:AtomicInteger、AtomicLong等。
2.可見性
多個執行緒訪問同一個共享變數時,其中一個執行緒對這個共享變數值的修改,其他執行緒能夠立刻獲得修改以後的值
3.有序性
編譯器和處理器為了最佳化程式效能而對指令序列進行重排序,也就是你編寫的程式碼順序和最終執行的指令順序是不一致的。
但是重排序過程不會影響到單執行緒程式的執行,卻會影響到多執行緒併發執行的正確性。
Volatile
Volatile 是一個Java語言的型別修飾符,一旦一個共享變數(類的成員變數、類的靜態成員變數)被Volatile修飾之後,那麼就具備了兩層語義:
1、保證多執行緒下的可見性
2、禁止進行指令重排序(即保證有序性)
這裡需要注意一個問題,Volatile只能讓被他修飾內容具有可見性、有序性。
Volatile只能保證對單次讀/寫的原子性,i++ 這種操作不能保證原子性。
Volatile的記憶體模型
Java 記憶體模型(JMM)是一種抽象的概念,並不真實存在,它描述了一組規則或規範,透過這組規範定義了程式中各個變數(包括例項欄位、靜態欄位和構成陣列物件的元素)的訪問方式。
試圖遮蔽各種硬體和作業系統的記憶體訪問差異,以實現讓 Java 程式在各種平臺下都能達到一致的記憶體訪問效果。
Java記憶體模型規定了所有的變數都儲存在主記憶體中,每條執行緒還有自己的工作記憶體,執行緒的工作記憶體中儲存了該執行緒中是用到的變數的主記憶體副本複製,執行緒對變數的所有操作都必須在工作記憶體中進行,而不能直接讀寫主記憶體。不同的執行緒之間也無法直接訪問對方工作記憶體中的變數,執行緒間變數的傳遞均需要自己的工作記憶體和主存之間進行資料同步進行。
- 主記憶體主要儲存的是Java例項物件,所有執行緒建立的例項物件都存放在主記憶體中,不管該例項物件是成員變數還是方法中的本地變數(也稱區域性變數),當然也包括了共享的類資訊、常量、靜態變數。由於是共享資料區域,多條執行緒對同一個變數進行訪問可能會發現執行緒安全問題。
- 工作記憶體每條執行緒都有自己的工作記憶體(Working Memory,又稱本地記憶體,可與前面介紹的處理器快取記憶體類比),執行緒的工作記憶體中儲存了該執行緒使用到的變數的主記憶體中的共享變數的副本複製。工作記憶體是 JMM 的一個抽象概念,並不真實存在。它涵蓋了快取,寫緩衝區,暫存器以及其他的硬體和編譯器最佳化。
主要儲存當前方法的所有本地變數資訊(工作記憶體中儲存著主記憶體中的變數副本複製),每個執行緒只能訪問自己的工作記憶體,即執行緒中的本地變數對其它執行緒是不可見的,就算是兩個執行緒執行的是
一段程式碼,它們也會各自在自己的工作記憶體中建立屬於當前執行緒的本地變數,當然也包括了位元組碼行號指示器、相關Native方法的資訊。
Volatile的實現原理
Volatile 保證記憶體可見性
主記憶體和工作記憶體之間的互動有具體的互動協議,JMM定義了八種操作來完成,這八種操作是原子的、不可再分的,它們分別是:lock,unlock,read,load,use,assign,store,write,其中lock,unlock,read,write作用於主記憶體;load,use,assign,store作用於工作記憶體。
(1) lock:將主記憶體中的變數鎖定,為一個執行緒所獨佔
(2) unclock:將lock加的鎖定解除,此時其它的執行緒可以有機會訪問此變數
(3) read:將主記憶體中的變數值讀到工作記憶體當中
(4) load:將read讀取的值儲存到工作記憶體中的變數副本中。
(5) use:將值傳遞給執行緒的程式碼執行引擎
(6) assign:將執行引擎處理返回的值重新賦值給變數副本
(7) store:將變數副本的值儲存到主記憶體中。
(8) write:將store儲存的值寫入到主記憶體的共享變數當中。
- 從主存複製變數到當前工作記憶體(read and load)
- 執行程式碼,改變共享變數值 (use and assign)
- 用工作記憶體資料重新整理主存相關內容 (store and write)
指令規則
- read 和 load、store和write必須成對出現
- assign操作,工作記憶體變數改變後必須刷回主記憶體
- 同一時間只能執行一個執行緒對變數進行lock,當前執行緒lock可重入,unlock次數必須等於lock的次數,該變數才能解鎖。
- 對一個變數lock後,會清空該執行緒工作記憶體變數的值,重新執行load或者assign操作初始化工作記憶體中變數的值。
- unlock前,必須將變數同步到主記憶體(store/write操作)
Volatile原始碼案例
以上就是Java併發程式設計之Volatile的實現原理介紹,希望對你有所收穫!
-END-
-END-
關於作者: ,十餘年BAT架構經驗,資深技術專家,曾任職阿里、淘寶、百度。
歡迎關注個人公眾號: mikechen的網際網路架構,十餘年BAT架構經驗傾囊相授!
在公眾號選單欄對話方塊回覆【 架構】關鍵詞,即可檢視我原創的 300期+BAT架構技術系列文章與1000+大廠面試題答案合集。
來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/70011997/viewspace-2852530/,如需轉載,請註明出處,否則將追究法律責任。
相關文章
- HashMap的實現原理(看這篇就夠了)HashMap
- 【劍指Offer】Redis 分散式鎖的實現原理看這篇就夠了Redis分散式
- mongoDB看這篇就夠了MongoDB
- Oracle索引,看這篇就夠了Oracle索引
- webpack的入門實踐,看這篇就夠了Web
- kafka3.x原理詳解看這篇就夠了Kafka
- 入門Webpack,看這篇就夠了Web
- Android Fragment看這篇就夠了AndroidFragment
- OAuth授權|看這篇就夠了OAuth
- Zookeeper入門看這篇就夠了
- Git 看這一篇就夠了Git
- React入門看這篇就夠了React
- JavaScript正則,看這篇就夠了JavaScript
- 小程式分享,看這篇就夠了
- Flutter DataTable 看這一篇就夠了Flutter
- 小程式入門看這篇就夠了
- Java 動態代理,看這篇就夠了Java
- vue 元件通訊看這篇就夠了Vue元件
- java序列化,看這篇就夠了Java
- 代理模式看這一篇就夠了模式
- EFCore 6.0入門看這篇就夠了
- ZooKeeper分散式配置——看這篇就夠了分散式
- Java 集合看這一篇就夠了Java
- 學習Less-看這篇就夠了
- [收藏]學習sed,看這篇就夠了
- 想要快速實現告警管理?來看這一篇就夠了!
- XLNet預訓練模型,看這篇就夠了!(程式碼實現)模型
- 一致性hash原理 看這一篇就夠了
- 關於GC原理和效能調優實踐,看這一篇就夠了!GC
- 瞭解 MongoDB 看這一篇就夠了MongoDB
- 關於SwiftUI,看這一篇就夠了SwiftUI
- 前端er瞭解GraphQL,看這篇就夠了前端
- 入門Hbase,看這一篇就夠了
- macOS 安裝 Nebula Graph 看這篇就夠了Mac
- jQuery入門看這一篇就夠了jQuery
- Elasticsearch入門,看這一篇就夠了Elasticsearch
- ActiveMq 之JMS 看這一篇就夠了MQ
- Redis主從複製看這篇就夠了Redis