Java Volatile關(guān)鍵字你真的了解嗎
正文
在談 Volatile 之前,我們先回顧下 Java 內(nèi)存模型 的三要素:原子性、可見性、有序性,也就是大家常提到的并發(fā)編程三要素。
并發(fā)編程的三要素
1.原子性
和數(shù)據(jù)庫(kù)事務(wù)中的原子性一樣,滿足原子性特性的操作是不可中斷的,要么全部執(zhí)行成功要么全部執(zhí)行失敗。
只有簡(jiǎn)單的讀取、賦值(而且必須是將數(shù)字賦值給某個(gè)變量,變量之間的相互賦值不是原子操作)才是原子操作。
比如:
i = 2;j = i;i++;i = i + 1;
上面4個(gè)操作中,i=2是讀取操作,必定是原子性操作,j=i你以為是原子性操作,其實(shí)吧,分為兩步,一是讀取i的值,然后再賦值給j,這就是2步操作了,稱不上原子操作,i++和i = i + 1其實(shí)是等效的,讀取i的值,加1,再寫回主存,那就是3步操作了。
所以上面的舉例中,最后的值可能出現(xiàn)多種情況,就是因?yàn)闈M足不了原子性。
非原子操作都會(huì)存在線程安全問題,需要我們使用同步技術(shù)(sychronized)來讓它變成一個(gè)原子操作,java的concurrent包下提供了一些原子類:比如:AtomicInteger
、AtomicLong
等。
2.可見性
多個(gè)線程訪問同一個(gè)共享變量時(shí),其中一個(gè)線程對(duì)這個(gè)共享變量值的修改,其他線程能夠立刻獲得修改以后的值。
3.有序性
編譯器和處理器為了優(yōu)化程序性能而對(duì)指令序列進(jìn)行重排序,也就是你編寫的代碼順序和最終執(zhí)行的指令順序是不一致的。但是重排序過程不會(huì)影響到單線程程序的執(zhí)行,卻會(huì)影響到多線程并發(fā)執(zhí)行的正確性。
Volatile
Volatile?是一個(gè)Java語(yǔ)言的類型修飾符,一旦一個(gè)共享變量(類的成員變量、類的靜態(tài)成員變量)被Volatile修飾之后,那么就具備了兩層語(yǔ)義:
1、保證多線程下的可見性
2、禁止進(jìn)行指令重排序(即保證有序性)
這里需要注意一個(gè)問題,Volatile 只能讓被他修飾內(nèi)容具有可見性、有序性 。Volatile只能保證對(duì)單次讀/寫的原子性,i++ 這種操作不能保證原子性。
Volatile 的內(nèi)存模型
**Java 內(nèi)存模型(JMM)**是一種抽象的概念,并不真實(shí)存在,它描述了一組規(guī)則或規(guī)范,通過這組規(guī)范定義了程序中各個(gè)變量(包括實(shí)例字段、靜態(tài)字段和構(gòu)成數(shù)組對(duì)象的元素)的訪問方式。
試圖屏蔽各種硬件和操作系統(tǒng)的內(nèi)存訪問差異,以實(shí)現(xiàn)讓 Java 程序在各種平臺(tái)下都能達(dá)到一致的內(nèi)存訪問效果。
Java內(nèi)存模型規(guī)定了所有的變量都存儲(chǔ)在主內(nèi)存中,每條線程還有自己的工作內(nèi)存,線程的工作內(nèi)存中保存了該線程中是用到的變量的主內(nèi)存副本拷貝,線程對(duì)變量的所有操作都必須在工作內(nèi)存中進(jìn)行,而不能直接讀寫主內(nèi)存。不同的線程之間也無(wú)法直接訪問對(duì)方工作內(nèi)存中的變量,線程間變量的傳遞均需要自己的工作內(nèi)存和主存之間進(jìn)行數(shù)據(jù)同步進(jìn)行。
- 主內(nèi)存?主要存儲(chǔ)的是Java實(shí)例對(duì)象,所有線程創(chuàng)建的實(shí)例對(duì)象都存放在主內(nèi)存中,不管該實(shí)例對(duì)象是成員變量還是方法中的本地變量(也稱局部變量),當(dāng)然也包括了共享的類信息、常量、靜態(tài)變量。由于是共享數(shù)據(jù)區(qū)域,多條線程對(duì)同一個(gè)變量進(jìn)行訪問可能會(huì)發(fā)現(xiàn)線程安全問題。
- 工作內(nèi)存?每條線程都有自己的工作內(nèi)存(Working Memory,又稱本地內(nèi)存,可與前面介紹的處理器高速緩存類比),線程的工作內(nèi)存中保存了該線程使用到的變量的主內(nèi)存中的共享變量的副本拷貝。
工作內(nèi)存是 JMM 的一個(gè)抽象概念,并不真實(shí)存在 。它涵蓋了緩存,寫緩沖區(qū),寄存器以及其他的硬件和編譯器優(yōu)化。
主要存儲(chǔ)當(dāng)前方法的所有本地變量信息(工作內(nèi)存中存儲(chǔ)著主內(nèi)存中的變量副本拷貝),每個(gè)線程只能訪問自己的工作內(nèi)存,即線程中的本地變量對(duì)其它線程是不可見的,就算是兩個(gè)線程執(zhí)行的是同一段代碼,它們也會(huì)各自在自己的工作內(nèi)存中創(chuàng)建屬于當(dāng)前線程的本地變量,當(dāng)然也包括了字節(jié)碼行號(hào)指示器、相關(guān)Native方法的信息。
Volatile 的實(shí)現(xiàn)原理
Volatile 保證內(nèi)存可見性
主內(nèi)存和工作內(nèi)存之間的交互有具體的交互協(xié)議,JMM定義了?八種操作?來完成,這八種操作是 原子的 、 不可再分的 ,它們分別是:lock
,unlock
,read
,load
,use
,assign
,store
,write
。
其中,lock , unlock , read , write 作用于主內(nèi)存; load ,use , assign , store 作用于工作內(nèi)存。
(1) lock
將主內(nèi)存中的變量鎖定,為一個(gè)線程所獨(dú)占。
(2) unclock
將lock加的鎖定解除,此時(shí)其它的線程可以有機(jī)會(huì)訪問此變量。
(3) read
將主內(nèi)存中的變量值讀到工作內(nèi)存當(dāng)中。
(4) load
將read讀取的值保存到工作內(nèi)存中的變量副本中。
(5) use
將值傳遞給線程的代碼執(zhí)行引擎。
(6) assign
將執(zhí)行引擎處理返回的值重新賦值給變量副本。
(7) store
將變量副本的值存儲(chǔ)到主內(nèi)存中。
(8) write
將 store 存儲(chǔ)的值寫入到主內(nèi)存的共享變量當(dāng)中。
- 從主存復(fù)制變量到當(dāng)前工作內(nèi)存(read and load)
- 執(zhí)行代碼,改變共享變量值 (use and assign)
- 用工作內(nèi)存數(shù)據(jù)刷新主存相關(guān)內(nèi)容 (store and write)
指令規(guī)則
- read 和 load、store 和 write 必須成對(duì)出現(xiàn)。
- assign 操作,工作內(nèi)存變量改變后必須刷回主內(nèi)存。
- 同一時(shí)間只能運(yùn)行一個(gè)線程對(duì)變量進(jìn)行 lock,當(dāng)前線程 lock 可重入,unlock 次數(shù)必須等于 lock 的次數(shù),該變量才能解鎖。
- 對(duì)一個(gè)變量 lock 后,會(huì)清空該線程工作內(nèi)存變量的值,重新執(zhí)行 load 或者 assign 操作初始化工作內(nèi)存中變量的值。
- unlock 前,必須將變量同步到主內(nèi)存( store/write 操作)。
Volatile源碼案例
總結(jié)
本篇文章就到這里了,希望能夠給你帶來幫助,也希望您能夠多多關(guān)注腳本之家的更多內(nèi)容!
相關(guān)文章
SpringBoot整合Hashids實(shí)現(xiàn)數(shù)據(jù)ID加密隱藏的全過程
這篇文章主要為大家詳細(xì)介紹了SpringBoot整合Hashids實(shí)現(xiàn)數(shù)據(jù)ID加密隱藏的全過程,文中的示例代碼講解詳細(xì),對(duì)大家的學(xué)習(xí)或工作有一定的幫助,感興趣的小伙伴可以跟隨小編一起學(xué)習(xí)一下2024-01-01Spring中自定義數(shù)據(jù)類型轉(zhuǎn)換的方法詳解
Spring3引入了一個(gè)core.onvert包,提供一個(gè)通用類型轉(zhuǎn)換系統(tǒng)。在Spring容器中,可以使用這個(gè)系統(tǒng)作為PropertyEditor實(shí)現(xiàn)的替代,將外部化的bean屬性值字符串轉(zhuǎn)換為所需的屬性類型。本文將詳解這一系統(tǒng)的使用方法,需要的可以參考一下2022-06-06java控制臺(tái)版實(shí)現(xiàn)五子棋游戲
這篇文章主要為大家詳細(xì)介紹了java控制臺(tái)版實(shí)現(xiàn)五子棋游戲,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2020-12-12JavaSE實(shí)現(xiàn)圖書管理系統(tǒng)的示例代碼
這篇博客是在學(xué)習(xí)了一部分Java基礎(chǔ)語(yǔ)法之后的練習(xí)項(xiàng)目,通過這個(gè)小項(xiàng)目的練習(xí),對(duì)Java中的類和對(duì)象,抽象類和接口等進(jìn)行熟悉理解??旄S小編一起學(xué)習(xí)學(xué)習(xí)吧2022-08-08給JavaBean賦默認(rèn)值并且轉(zhuǎn)Json字符串的實(shí)例
這篇文章主要介紹了給JavaBean賦默認(rèn)值并且轉(zhuǎn)Json字符串的實(shí)例,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2022-03-03從application.properties配置文件獲取的漢字亂碼的解決方法
平時(shí)從配置文件各種讀取配置參數(shù)都正常,但是有時(shí)候放了個(gè)中文就亂碼,你肯定試過網(wǎng)上好多方法,都沒解決,那么來看下面,恭喜你終于找這里了,本文給大家介紹了從application.properties配置文件獲取的漢字亂碼的解決方法,需要的朋友可以參考下2024-03-03使用Swagger實(shí)現(xiàn)接口版本號(hào)管理方式
這篇文章主要介紹了使用Swagger實(shí)現(xiàn)接口版本號(hào)管理方式,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2021-10-10