Java中volatile關(guān)鍵字的線程的可見性、有序性詳解
引言
在juc多線程并發(fā)編程中,常常需要關(guān)注線程的“可見性”與“有序性”。本文將詳細(xì)介紹這兩部分內(nèi)容,以及volatile關(guān)鍵字的使用。
閱讀本文前需要一些jvm運(yùn)行時(shí)內(nèi)存、進(jìn)程與線程、共享內(nèi)存、鎖等相關(guān)知識(shí)。
1、可見性
1.1 定義
當(dāng)一個(gè)對象在多個(gè)內(nèi)存中都存在副本時(shí),如果一個(gè)內(nèi)存修改了共享變量,其它線程也應(yīng)該能夠看到被修改后的值。
1.2 解釋
解釋這句話,首先我們要知道,進(jìn)程是操作系統(tǒng)分配資源的最小單位,在每個(gè)進(jìn)程里,都包含有數(shù)據(jù)共享的區(qū)域(例如其中有成員變量、堆、靜態(tài)常量等,詳情查閱jvm運(yùn)行時(shí)內(nèi)存),并可能包含多個(gè)線程。 在jvm底層,每個(gè)線程想要操作某個(gè)共享變量時(shí),不能夠直接操作共享區(qū)域的“主存”,而是要先把數(shù)據(jù)從主存讀到線程自己的高速緩存中,再進(jìn)行操作,再賦值回去。
“具體來說,當(dāng)一個(gè)線程要讀取某個(gè)變量的值時(shí),它首先檢查自己的工作緩存中是否存在該變量的副本。 如果存在,則直接讀取副本的值; 如果不存在,則從主內(nèi)存中獲取該變量的最新值,并將其復(fù)制到自己的工作緩存中。 類似地,當(dāng)一個(gè)線程要修改某個(gè)變量的值時(shí),它首先會(huì)在自己的工作緩存中修改副本,再根據(jù)一定的策略將變化同步回主內(nèi)存。”
那么可見性的概念相應(yīng)而來:多個(gè)線程都存了同一個(gè)對象副本,如果此時(shí)有一個(gè)內(nèi)存修改了共享變量,其他的線程應(yīng)該能夠“看到”,而不是傻b一樣仍舊拿著自己緩存里的值操作了。
1.3 實(shí)例說明
例如下面情況,如果main 線程對 run 變量的修改對于 t 線程不可見,導(dǎo)致了 t 線程無法停止:
其原因是t線程頻繁從主內(nèi)存中讀取 run 的值,JIT 編譯器會(huì)將 run 的值緩存至自己工作內(nèi)存中的高速緩存中, 減少對主存中 run 的訪問。 那么,當(dāng)主線程(也就是其他線程)把run變?yōu)閒alse,由于子線程t(當(dāng)前線程)仍然從高速緩存里讀的run,會(huì)認(rèn)為run并沒有改變,從而邏輯出現(xiàn)問題。
1.4 解決方法:volatile(易變關(guān)鍵字)
作用:volatile可以用來修飾成員變量和靜態(tài)成員變量,他可以避免線程從自己的工作緩存中查找變量的值,必須到主存中獲取它的值。(關(guān)鍵點(diǎn))
然而volatile并不能保證原子性,例如不加鎖的多線程執(zhí)行函數(shù),對成員變量i進(jìn)行i++ i--,最后得到的結(jié)果i不一定是不變的。 因?yàn)関olatile關(guān)鍵字,只能讓該線程在使用數(shù)據(jù)前強(qiáng)制從主存讀取,但讀取后是否發(fā)生改變是看不見的。如果有別的線程在該線程讀取后成功改變了共享內(nèi)存的值,還是會(huì)發(fā)生指令交錯(cuò)。
2、有序性
在多線程環(huán)境下,由于編譯器和處理器對指令進(jìn)行優(yōu)化和重排序,可能會(huì)導(dǎo)致操作的執(zhí)行順序發(fā)生改變,從而違反了代碼編寫的原始順序。
為了保證有序性,可以使用以下方法:
使用volatile關(guān)鍵字:對關(guān)鍵的共享變量使用volatile關(guān)鍵字進(jìn)行聲明,以確保對該變量的寫操作立即對其他線程可見。
使用synchronized關(guān)鍵字或Lock機(jī)制:通過使用同步塊或鎖來保護(hù)共享資源的讀寫操作,確保線程之間的有序訪問。
使用原子類:Java提供了一些原子類(如AtomicInteger、AtomicLong等)來進(jìn)行原子操作,這些原子類的方法能夠保證操作的原子性、可見性和有序性。
簡單來說volatile避免了指令重排,也就避免了多線程中可能產(chǎn)生的問題。
到此這篇關(guān)于Java中volatile關(guān)鍵字的線程的可見性、有序性詳解的文章就介紹到這了,更多相關(guān)volatile的線程的可見性、有序性內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Java Swing實(shí)現(xiàn)餐廳點(diǎn)餐系統(tǒng)源碼(收藏版)
這篇文章主要介紹了Java Swing實(shí)現(xiàn)餐廳點(diǎn)餐系統(tǒng)源碼,本文給大家介紹的非常詳細(xì),對大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2021-02-02Java調(diào)用opencv IDEA環(huán)境配置的教程詳解
這篇文章主要為大家詳細(xì)介紹了Java調(diào)用opencv IDEA環(huán)境配置的相關(guān)知識(shí),文中的示例代碼講解詳細(xì),感興趣的小伙伴可以跟隨小編一起學(xué)習(xí)一下2024-03-03SpringCloud配置客戶端ConfigClient接入服務(wù)端
這篇文章主要為大家介紹了SpringCloud配置客戶端ConfigClient接入服務(wù)端,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2023-08-08Java利用JSONPath操作JSON數(shù)據(jù)的技術(shù)指南
JSONPath?是一種強(qiáng)大的工具,用于查詢和操作?JSON?數(shù)據(jù),類似于?SQL?的語法,它為處理復(fù)雜的?JSON?數(shù)據(jù)結(jié)構(gòu)提供了簡單且高效的解決方案,本文將介紹?JSONPath?的基本語法,并通過詳細(xì)的?Java?示例展示其實(shí)際應(yīng)用,需要的朋友可以參考下2025-04-04代理模式:JAVA靜態(tài)代理和動(dòng)態(tài)代理的實(shí)例和實(shí)現(xiàn)詳解
這篇文章主要給大家介紹了關(guān)于Java靜態(tài)代理和動(dòng)態(tài)代理的相關(guān)資料,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2021-08-08Java移除無效括號(hào)的方法實(shí)現(xiàn)
本文主要介紹了Java移除無效括號(hào)的方法實(shí)現(xiàn),文中通過示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2021-08-08SpringCloud Alibaba使用Seata處理分布式事務(wù)的技巧
在傳統(tǒng)的單體項(xiàng)目中,我們使用@Transactional注解就能實(shí)現(xiàn)基本的ACID事務(wù)了,隨著微服務(wù)架構(gòu)的引入,需要對數(shù)據(jù)庫進(jìn)行分庫分表,每個(gè)服務(wù)擁有自己的數(shù)據(jù)庫,這樣傳統(tǒng)的事務(wù)就不起作用了,那么我們?nèi)绾伪WC多個(gè)服務(wù)中數(shù)據(jù)的一致性呢?跟隨小編一起通過本文了解下吧2021-06-06Java 基于AQS實(shí)現(xiàn)一個(gè)同步器
這篇文章主要介紹了如何基于AQS實(shí)現(xiàn)一個(gè)同步器,幫助大家更好的理解和學(xué)習(xí)Java并發(fā),感興趣的朋友可以了解下2020-09-09