這篇文章主要介紹“Volatile的作用是什么”,在日常操作中,相信很多人在Volatile的作用是什么問題上存在疑惑,小編查閱了各式資料,整理出簡單好用的操作方法,希望對大家解答”Volatile的作用是什么”的疑惑有所幫助!接下來,請跟著小編一起來學(xué)習(xí)吧!
網(wǎng)站的建設(shè)創(chuàng)新互聯(lián)專注網(wǎng)站定制,經(jīng)驗豐富,不做模板,主營網(wǎng)站定制開發(fā).小程序定制開發(fā),H5頁面制作!給你煥然一新的設(shè)計體驗!已為混凝土攪拌罐車等企業(yè)提供專業(yè)服務(wù)。
其實早期計算機中cpu和內(nèi)存的速度是差不多的,但在現(xiàn)代計算機中,cpu的指令速度遠超內(nèi)存的存取速度,由于計算機的儲存設(shè)備與處理器的運算速度有幾個數(shù)量級的差距,所以現(xiàn)代計算機系統(tǒng)都不得不加入一層讀寫速度盡可能接近處理器運算速度的 高速緩存(Cach)來作為內(nèi)存與處理器之間的緩沖。
將運算需要使用到的數(shù)據(jù)復(fù)制到緩存中,讓運算能快速進行,當運算結(jié)束后再從緩存同步回內(nèi)存之中,這樣處理器就無需等待緩慢的內(nèi)存讀寫了。
基于高速緩存的存儲交互很好地解決了處理器與內(nèi)存的速度矛盾,但是也為計算機系統(tǒng)帶來更高的復(fù)雜度,因為它引入一個新的問題:緩存一致性(CacheCoherence)。
在多處理器系統(tǒng)中,每個處理器都有自己的高速緩存,而它們又共享同一主內(nèi)存(MainMemory)。
JMM:Java 內(nèi)存模型,是java虛擬機規(guī)范中所定義的一種內(nèi)存模型,Java 內(nèi)存模型是標準化的,屏蔽掉了底層不同計算機的區(qū)別。描述了Java程序中各種變量(線程共享變量)的訪問規(guī)則,以及在JVM中將變量,儲存到內(nèi)存和從內(nèi)存中讀取變量這樣的底層細節(jié)。
所有的共享變量都儲存于主內(nèi)存,這里所說的變量指的是實例變量和類變量,不包含局部變量,因為局部變量是線程私有的,因此不存在競爭問題。
每一個線程還存在自己的工作內(nèi)存,線程的工作內(nèi)容,保留了被線程使用的變量的工作副本。
線程對變量的所有操作(讀、取)都必須在工作內(nèi)存中完成,而不能直接讀寫主內(nèi)存中的變量。
不同線程之間也不能直接訪問對方工作內(nèi)存中的變量,線程間變量的值的傳遞需要通過主內(nèi)存中轉(zhuǎn)來完成。
正是因為這樣的機制,才導(dǎo)致了可見性問題的存在。
/** * @Author 庭前云落 * @Date 2020/10/1 10:43 * @Description */ public class Test { public static void main(String[] args) { Tqyl a = new Tqyl(); a.start(); for (; ; ) { synchronized (a) { if (a.isFlag()) { System.out.println("庭前云落"); } } } } }
因為某一個線程進入synchronized代碼塊前后,線程會獲得鎖,清空工作內(nèi)存,從主內(nèi)存拷貝共享變量最新的值到工作內(nèi)存成為副本,執(zhí)行代碼,將修改后的副本的值刷新回主內(nèi)存中,線程釋放鎖。
而獲取不到鎖的線程會阻塞等待,所以變量的值肯定一直都是最新的。
/** * @Author 庭前云落 * @Date 2020/10/1 10:44 * @Description */ public class Tqyl extends Thread { private volatile boolean flag = false; public boolean isFlag() { return flag; } @Override public void run() { try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } flag=true; System.out.println("flag="+flag); } }
每個線程操作數(shù)據(jù)的時候會把數(shù)據(jù)從主內(nèi)存讀取到自己的工作內(nèi)存,如果它操作了數(shù)據(jù)并且寫回了,其它已經(jīng)讀取的線程的變量副本就會失效了,需要讀數(shù)據(jù)進操作又要再次去主內(nèi)存中讀取了。
volatile保證不同線程對共享變量操作的可見性,也就是說一個線程修改了volatile修飾的變量,當修改寫回主內(nèi)存時,另外一個線程立即看到最新的值。
之前我們說過當多個處理器的運算任務(wù)都涉及同一塊主內(nèi)存區(qū)域時,將可能導(dǎo)致各自的緩存數(shù)據(jù)不一致,舉例說明變量在多個CPU之間的共享。
如果真的發(fā)生這種情況,那同步回到主內(nèi)存時以誰的緩存數(shù)據(jù)為準呢?
為了解決一致性的問題,需要各個處理器訪問緩存時都遵循一些協(xié)議,在讀寫時要根據(jù)協(xié)議來進行操作,這類協(xié)議有 MSI、MESI(IllinoisProtocol)、MOSI、Synapse、Firefly及DragonProtocol等。
當CPU寫數(shù)據(jù)時,如果發(fā)現(xiàn)操作的變量是共享變量,即在其它CPU中野存在該變量的副本,會發(fā)出信息通知其它CPU將該變量的緩存行置為無效狀態(tài),因此當其他CPU需要讀取這個變量時,發(fā)現(xiàn)自己緩存中緩存該變量的緩存行是無效的,那么它就會從內(nèi)存重新讀取。
每個處理器通過嗅探在總線上傳播的數(shù)據(jù)來檢查自己緩存的值是不是過期了,當處理器發(fā)現(xiàn)自己緩存行對應(yīng)的內(nèi)存地址被修改,就會將當前處理器的緩存行設(shè)置成無效狀態(tài),當處理器對這個數(shù)據(jù)進行修改操作的時候,會重新從系統(tǒng)內(nèi)存中把數(shù)據(jù)讀到處理器緩存里。
由于Volatile的MESI緩存一致性協(xié)議,需要不斷的從主內(nèi)存嗅探和cas不斷循環(huán),無效交互會導(dǎo)致總線帶寬達到峰值。
所以不要大量使用Volatile,至于什么時候去使用Volatile什么時候使用鎖,根據(jù)場景區(qū)分。
CAS(Compare-and-Swap):即比較并替換,是一種實現(xiàn)并發(fā)算法時常用到的技術(shù),Java并發(fā)包中的很多類都使用了CAS技術(shù)。
為了提高性能,編譯器和處理器常常會對既定的代碼執(zhí)行順序進行指令重排序。
一個好的內(nèi)存模型實際上會放松對處理器和編譯器規(guī)則的束縛,也就是說軟件技術(shù)和硬件技術(shù)都為同一個目標,而進行奮斗:在不改變程序執(zhí)行結(jié)果的前提下,盡可能提高執(zhí)行效率。
JMM對底層盡量減少約束,使其能夠發(fā)揮自身優(yōu)勢。
因此,在執(zhí)行程序時,為了提高性能,編譯器和處理器常常會對指令進行重排序。
一般重排序可以分為如下三種:
編譯器優(yōu)化的重排序。編譯器在不改變單線程程序語義的前提下,可以重新安排語句的執(zhí)行順序;
指令級并行的重排序?,F(xiàn)代化處理器采用了指令級并行技術(shù)來將多條指令重疊執(zhí)行。如果不存在數(shù)據(jù)依賴性,處理器可以改變語句對應(yīng)機器指令的執(zhí)行順序;
內(nèi)存系統(tǒng)的重排序。由于處理器使用緩存和讀/寫緩沖區(qū),這使得加載和儲存操作看上去可能是在亂序執(zhí)行的。
這里還得提一個概念,as-if-serial。
不管怎么重排序,單線程下的執(zhí)行結(jié)果不能被改變。
編譯器、runtime和處理器都必須遵守as-if-serial語義。
那Volatile是怎么保證不會被執(zhí)行重排序的呢?
java編譯器會在生成指令系列時在適當?shù)奈恢脮迦?內(nèi)存屏障
指令來禁止特定類型的處理器重排序。
為了實現(xiàn)volatile的內(nèi)存語義,JMM會限制特定類型的編譯器和處理器重排序,JMM會針對編譯器制定volatile重排序規(guī)則表:
需要注意的是:volatile寫是在前?和后?分別插?內(nèi)存屏障,?volatile讀操作是在后面插?兩個內(nèi)存屏障。
寫
讀
上面的我提過重排序原則,為了提高處理速度,JVM會對代碼進行編譯優(yōu)化,也就是指令重排序優(yōu)化,并發(fā)編程下指令重排序會帶來一些安全隱患:如指令重排序?qū)е碌亩鄠€線程操作之間的不可見性。
如果讓程序員再去了解這些底層的實現(xiàn)以及具體規(guī)則,那么程序員的負擔就太重了,嚴重影響了并發(fā)編 程的效率。
從JDK5開始,提出了 happens-before 的概念,通過這個概念來闡述操作之間的內(nèi)存可?性。
如果一個操作執(zhí)行的結(jié)果需要對另一個操作可見,那么這兩個操作之間必須存在 happens-before關(guān)系。
volatile域規(guī)則:對一個 volatile 域的寫操作,happens-before 于任意線程后續(xù)對這個volatile域的讀。
如果現(xiàn)在我的flag變成了false,那么后面的那個操作,一定要知道我變了。
我們要知道Volatile是沒辦法保證原子性的,一定要保證原子性,可以使用其他方法。
就是一次操作,要么完全成功,要么完全失敗。
假設(shè)現(xiàn)在有 N 個線程對同一個變量進行累加也是沒辦法保證結(jié)果是對的,因為讀寫這個過程并不是原子性的。
要解決也簡單,要么用原子類,比如 AtomicInteger,要么加鎖(記得關(guān)注Atomic的底層)
/** * @Author 庭前云落 * @Date 2020/10/1 11:53 * @Description */ public class Singleton { //可見性和指令重排序都保證 private volatile static Singleton instance = null; //私有構(gòu)造 public Singleton() { } public static Singleton getInstance(){ //第一重檢查鎖定 if(instance==null){ //同步鎖定代碼塊 synchronized (Singleton.class){ //第二重檢查鎖定 if(instance==null){ //注意:非原子操作 instance = new Singleton(); } } } return instance; } }
為什么要雙重檢查?如果不用Volatile會怎么樣?
禁止指令重排序的好處。
對象實際上創(chuàng)建對象要經(jīng)過如下幾個步驟:
分配內(nèi)存空間
調(diào)用構(gòu)造器,初始化實例
返回地址給引用
是可能發(fā)生指令重排序的,那有可能構(gòu)造函數(shù)在對象初始化完成前就賦值完成了,在內(nèi)存里面開辟一片儲存區(qū)域后直接返回內(nèi)存的引用,這個時候還沒真正的初始化完對象。
但是別的線程去判斷 instance!=null,直接拿去用了,其實這個對象是個半成品,那就有空指針異常了。
可見性怎么保證的?
因為可見性,線程A在自己的內(nèi)存初始化了對象,還沒來得及寫回主內(nèi)存,B線程也這么做了,那就創(chuàng)建了多個對象,不是真正意義上單例了。
volatile只能修飾實例變量和類變量,而synchronized可以修飾方法,以及代碼塊。
volatile保證數(shù)據(jù)的可見性,但是不保證原子性(多線程進行寫操作,不保證線程安全);而synchronized是一種排他(互斥)的機制。volatile用于禁止指令重排序:可以解決單例雙重檢查對象初始化代碼執(zhí)行亂序問題。
volatile 可以看做輕量版的synchronized,volatile不保證原子性,但是如果是對一個共享變量進行多個線程的賦值,而沒有其它的操作,那么就可以用 volatile 來代替 synchronized,因為賦值本身就是有原子性的,而 volatile 又保證了可見性,所以就可以保證線程安全了。
volatile修飾符適用于以下的場景:
某個屬性被多個線程共享,其中有一個線程修改了此屬性,其它線程可以立即得到修改后的值,比如 booleanflag;或者作為觸發(fā)器,實現(xiàn)輕量級同步。
volatile屬性的讀寫操作都是無鎖的,它不能替代synchronized,因為它沒有提供原子性和互斥性。因為無鎖,不需要花費時間在獲取鎖和釋放鎖上,所以說它是低成本的。
volatile只能作用于屬性,我們用volatile修飾屬性,這樣compilers就不會對這個屬性做指令重排序。
volatile提供了可見性,任何一個線程對其的修改將立馬對其它線程可見,volatile屬性不會被線程緩存,始終從主存中讀取。
volatile提供了happens-before保證,對volatile變量v的寫入happens-before所有其他線程后續(xù)對v的讀寫操作。
volatile可以使得long和double的賦值是原子的。
volatile可以在單例雙重檢查中實現(xiàn)可見性和禁止指令重排序,從而保證安全性。
到此,關(guān)于“Volatile的作用是什么”的學(xué)習(xí)就結(jié)束了,希望能夠解決大家的疑惑。理論與實踐的搭配能更好的幫助大家學(xué)習(xí),快去試試吧!若想繼續(xù)學(xué)習(xí)更多相關(guān)知識,請繼續(xù)關(guān)注創(chuàng)新互聯(lián)網(wǎng)站,小編會繼續(xù)努力為大家?guī)砀鄬嵱玫奈恼拢?/p>
網(wǎng)站標題:Volatile的作用是什么
文章路徑:http://vcdvsql.cn/article46/gjideg.html
成都網(wǎng)站建設(shè)公司_創(chuàng)新互聯(lián),為您提供建站公司、移動網(wǎng)站建設(shè)、電子商務(wù)、服務(wù)器托管、網(wǎng)站設(shè)計、微信公眾號
聲明:本網(wǎng)站發(fā)布的內(nèi)容(圖片、視頻和文字)以用戶投稿、用戶轉(zhuǎn)載內(nèi)容為主,如果涉及侵權(quán)請盡快告知,我們將會在第一時間刪除。文章觀點不代表本網(wǎng)站立場,如需處理請聯(lián)系客服。電話:028-86922220;郵箱:631063699@qq.com。內(nèi)容未經(jīng)允許不得轉(zhuǎn)載,或轉(zhuǎn)載時需注明來源: 創(chuàng)新互聯(lián)