内射老阿姨1区2区3区4区_久久精品人人做人人爽电影蜜月_久久国产精品亚洲77777_99精品又大又爽又粗少妇毛片

Java多線程volatile關(guān)鍵字及內(nèi)存屏障實(shí)例分析

本篇內(nèi)容主要講解“Java多線程volatile關(guān)鍵字及內(nèi)存屏障實(shí)例分析”,感興趣的朋友不妨來(lái)看看。本文介紹的方法操作簡(jiǎn)單快捷,實(shí)用性強(qiáng)。下面就讓小編來(lái)帶大家學(xué)習(xí)“Java多線程volatile關(guān)鍵字及內(nèi)存屏障實(shí)例分析”吧!

創(chuàng)新互聯(lián)從2013年成立,是專業(yè)互聯(lián)網(wǎng)技術(shù)服務(wù)公司,擁有項(xiàng)目成都網(wǎng)站制作、成都做網(wǎng)站、外貿(mào)營(yíng)銷網(wǎng)站建設(shè)網(wǎng)站策劃,項(xiàng)目實(shí)施與項(xiàng)目整合能力。我們以讓每一個(gè)夢(mèng)想脫穎而出為使命,1280元城北做網(wǎng)站,已為上家服務(wù),為城北各地企業(yè)和個(gè)人服務(wù),聯(lián)系電話:028-86922220

volatile是JVM提供的一種最輕量級(jí)的同步機(jī)制,因?yàn)镴ava內(nèi)存模型為volatile定義特殊的訪問(wèn)規(guī)則,使其可以實(shí)現(xiàn)Java內(nèi)存模型中的兩大特性:可見(jiàn)性和有序性。正因?yàn)関olatile關(guān)鍵字具有這兩大特性,所以我們可以使用volatile關(guān)鍵字解決多線程中的某些同步問(wèn)題。

volatile的可見(jiàn)性

volatile的可見(jiàn)性是指當(dāng)一個(gè)變量被volatile修飾后,這個(gè)變量就對(duì)所有線程均可見(jiàn)。白話點(diǎn)就是說(shuō)當(dāng)一個(gè)線程修改了一個(gè)volatile修飾的變量后,其他線程可以立刻得知這個(gè)變量的修改,拿到最這個(gè)變量最新的值。

結(jié)合前一篇文章提到的Java內(nèi)存模型中線程、工作內(nèi)存、主內(nèi)存的交互關(guān)系,我們對(duì)volatile的可見(jiàn)性也可以這么理解,定義為volatile修飾的變量,在線程對(duì)其進(jìn)行寫入操作時(shí)不會(huì)把值緩存在工作內(nèi)存中,而是直接把修改后的值刷新回寫到主內(nèi)存,而當(dāng)處理器監(jiān)控到其他線程中該變量在主內(nèi)存中的內(nèi)存地址發(fā)生變化時(shí),會(huì)讓這些線程重新到主內(nèi)存中拷貝這個(gè)變量的最新值到工作內(nèi)存中,而不是繼續(xù)使用工作內(nèi)存中舊的緩存。

下面我列舉一個(gè)利用volatile可見(jiàn)性解決多線程并發(fā)安全的示例:

public class VolatileDemo { //private static boolean isReady = false; private static volatile boolean isReady = false; static class ReadyThread extends Thread {  public void run() {   while (!isReady) {       }   System.out.println("ReadyThread finish");  } } public static void main(String[] args) throws InterruptedException {  new ReadyThread().start();  Thread.sleep(1000);//sleep 1秒鐘確保ReadyThread線程已經(jīng)開始執(zhí)行  isReady = true; }}

上面這段代碼運(yùn)行之后最終會(huì)在控制臺(tái)打印出: ReadyThread finish ,而當(dāng)你將變量isReady的volatile修飾符去掉之后再運(yùn)行則會(huì)發(fā)現(xiàn)程序一直運(yùn)行而不結(jié)束,而控制臺(tái)也沒(méi)有任何打印輸出。

我們分析下這個(gè)程序:初始時(shí)isReady為false,所以ReadyThread線程啟動(dòng)開始執(zhí)行后,它的while代碼塊因標(biāo)志位isReady為false會(huì)進(jìn)入死循環(huán),當(dāng)用volatile關(guān)鍵字修飾isReady時(shí),main方法所在的線程將isReady修改為true之后,ReadyThread線程會(huì)立刻得知并獲取這個(gè)最新的isReady值,緊接著while循環(huán)就會(huì)結(jié)束循環(huán),所以最后打印出了相關(guān)文字。而當(dāng)未用volatile修飾時(shí),main方法所在的線程雖然修改了isReady變量,但ReadyThread線程并不知道這個(gè)修改,所以使用的還是之前的舊值,因此會(huì)一直死循環(huán)執(zhí)行while語(yǔ)句。

volatile的有序性

有序性是指程序代碼的執(zhí)行是按照代碼的實(shí)現(xiàn)順序來(lái)按序執(zhí)行的。

volatile的有序性特性則是指禁止JVM指令重排優(yōu)化。

我們來(lái)看一個(gè)例子:

public class Singleton { private static Singleton instance = null; //private static volatile Singleton instance = null; private Singleton() { } public static Singleton getInstance() {  //第一次判斷  if(instance == null) {   synchronized (Singleton.class) {    if(instance == null) {     //初始化,并非原子操作     instance = new Singleton();     }   }  }  return instance; }}

上面的代碼是一個(gè)很常見(jiàn)的單例模式實(shí)現(xiàn)方式,但是上述代碼在多線程環(huán)境下是有問(wèn)題的。為什么呢,問(wèn)題出在instance對(duì)象的初始化上,因?yàn)?code> instance = new Singleton(); 這個(gè)初始化操作并不是原子的,在JVM上會(huì)對(duì)應(yīng)下面的幾條指令:

memory =allocate(); //1. 分配對(duì)象的內(nèi)存空間 ctorInstance(memory); //2. 初始化對(duì)象 instance =memory;  //3. 設(shè)置instance指向剛分配的內(nèi)存地址

上面三個(gè)指令中,步驟2依賴步驟1,但是步驟3不依賴步驟2,所以JVM可能針對(duì)他們進(jìn)行指令重拍序優(yōu)化,重排后的指令如下:

memory =allocate(); //1. 分配對(duì)象的內(nèi)存空間 instance =memory;  //3. 設(shè)置instance指向剛分配的內(nèi)存地址ctorInstance(memory); //2. 初始化對(duì)象

這樣優(yōu)化之后,內(nèi)存的初始化被放到了instance分配內(nèi)存地址的后面,這樣的話當(dāng)線程1執(zhí)行步驟3這段賦值指令后,剛好有另外一個(gè)線程2進(jìn)入getInstance方法判斷instance不為null,這個(gè)時(shí)候線程2拿到的instance對(duì)應(yīng)的內(nèi)存其實(shí)還未初始化,這個(gè)時(shí)候拿去使用就會(huì)導(dǎo)致出錯(cuò)。

所以我們?cè)谟眠@種方式實(shí)現(xiàn)單例模式時(shí),會(huì)使用volatile關(guān)鍵字修飾instance變量,這是因?yàn)関olatile關(guān)鍵字除了可以保證變量可見(jiàn)性之外,還具有防止指令重排序的作用。當(dāng)用volatile修飾instance之后,JVM執(zhí)行時(shí)就不會(huì)對(duì)上面提到的初始化指令進(jìn)行重排序優(yōu)化,這樣也就不會(huì)出現(xiàn)多線程安全問(wèn)題了。

volatile使用場(chǎng)景

volatile的可以在以下場(chǎng)景中使用:

當(dāng)運(yùn)算結(jié)果不依賴變量當(dāng)前的值,或者能確保只有單一線程修改變量的值的時(shí)候,我們才可以對(duì)該變量使用volatile關(guān)鍵字 變量不需要與其他狀態(tài)變量共同參與不變約束

volatile與原子性

volatile關(guān)鍵字能保證變量的可見(jiàn)性和代碼的有序性,但是不能保證變量的原子性,下面我再舉一個(gè)volatile與原子性的例子:

public class VolatileTest { public static volatile int count = 0; public static void increase() {  count++; } public static void main(String[] args) {  Thread[] threads = new Thread[20];  for(int i = 0; i < threads.length; i++) {   threads[i] = new Thread(() -> {    for(int j = 0; j < 1000; j++) {     increase();    }   });   threads[i].start();  }  //等待所有累加線程結(jié)束  while (Thread.activeCount() > 1) {   Thread.yield();  }  System.out.println(count); }}

上面這段代碼創(chuàng)建了20個(gè)線程,每個(gè)線程對(duì)變量count進(jìn)行1000次自增操作,如果這段代碼并發(fā)正常的話,結(jié)果應(yīng)該是20000,但實(shí)際運(yùn)行過(guò)程中經(jīng)常會(huì)出現(xiàn)小于20000的結(jié)果,因?yàn)閏ount++這個(gè)自增操作不是原子操作。

上面的count++自增操作等價(jià)于count=count+1,所以JVM需要先讀取count的值,然后在count的基礎(chǔ)上給它加1,然后再將新的值重新賦值給count變量,所以這個(gè)自增總共需要三步。

上圖中我將線程對(duì)count的自增操作畫了個(gè)簡(jiǎn)單的流程,一個(gè)線程要對(duì)count進(jìn)行自增時(shí)要先讀取count的值,然后在當(dāng)前count值的基礎(chǔ)上進(jìn)行count+1操作,最后將count的新值重新寫回到count。

如果線程2在線程1讀取count舊值寫回count新值期間讀取count的值,顯然這個(gè)時(shí)候線程2讀取的是count還未更新的舊值,這時(shí)兩個(gè)線程是對(duì)同一個(gè)值進(jìn)行了+1操作,這樣這兩個(gè)線程就沒(méi)有對(duì)count實(shí)現(xiàn)累加效果,相反這些操作卻又沒(méi)有違反volatile的定義,所以這種情況下使用volatile依然會(huì)存在多線程并發(fā)安全的問(wèn)題。

volatile與內(nèi)存屏障

前面介紹了volatile的可見(jiàn)性和有序性,那JVM到底是如何為volatile關(guān)鍵字實(shí)現(xiàn)的這兩大特性呢,Java內(nèi)存模型其實(shí)是通過(guò)內(nèi)存屏障(Memory Barrier)來(lái)實(shí)現(xiàn)的。

內(nèi)存屏障其實(shí)也是一種JVM指令,Java內(nèi)存模型的重排規(guī)則會(huì)要求Java編譯器在生成JVM指令時(shí)插入特定的內(nèi)存屏障指令,通過(guò)這些內(nèi)存屏障指令來(lái)禁止特定的指令重排序。

另外內(nèi)存屏障還具有一定的語(yǔ)義:內(nèi)存屏障之前的所有寫操作都要回寫到主內(nèi)存,內(nèi)存屏障之后的所有讀操作都能獲得內(nèi)存屏障之前的所有寫操作的最新結(jié)果(實(shí)現(xiàn)了可見(jiàn)性)。因此重排序時(shí),不允許把內(nèi)存屏障之后的指令重排序到內(nèi)存屏障之前。

下面的表是volatile有關(guān)的禁止指令重排的行為:

普通讀寫      可以重排      可以重排      不可以重排              volatile讀      不可以重排      不可以重排      不可以重排              volatile寫      可以重排      不可以重排      不可以重排

從上面的表我們可以得出下面這些結(jié)論:

當(dāng)?shù)诙€(gè)操作volatile寫時(shí),不論第一個(gè)操作是什么,都不能重排序。這個(gè)規(guī)則保證了volatile寫之前的操作不會(huì)被重排到volatile寫之后。

當(dāng)?shù)谝粋€(gè)操作為volatile讀時(shí),不論第二個(gè)操作是什么,都不能重排。這個(gè)操作保證了volatile讀之后的操作不會(huì)被重排到volatile讀之前。

當(dāng)?shù)谝粋€(gè)操作為volatile寫,第二個(gè)操作為volatile讀時(shí),不能重排。

JVM中提供了四類內(nèi)存屏障指令:

LoadLoad      Load1; LoadLoad; Load2      保證load1的讀取操作在load2及后續(xù)讀取操作之前執(zhí)行              StoreStore      Store1; StoreStore; Store2      在store2及其后的寫操作執(zhí)行前,保證store1的寫操作已刷新到主內(nèi)存              LoadStore      Load1; LoadStore; Store2      在stroe2及其后的寫操作執(zhí)行前,保證load1的讀操作已讀取結(jié)束              StoreLoad      Store1; StoreLoad; Load2      保證store1的寫操作已刷新到主內(nèi)存之后,load2及其后的讀操作才能執(zhí)行

到此,相信大家對(duì)“Java多線程volatile關(guān)鍵字及內(nèi)存屏障實(shí)例分析”有了更深的了解,不妨來(lái)實(shí)際操作一番吧!這里是創(chuàng)新互聯(lián)網(wǎng)站,更多相關(guān)內(nèi)容可以進(jìn)入相關(guān)頻道進(jìn)行查詢,關(guān)注我們,繼續(xù)學(xué)習(xí)!

網(wǎng)站題目:Java多線程volatile關(guān)鍵字及內(nèi)存屏障實(shí)例分析
本文URL:http://m.rwnh.cn/article14/jepode.html

成都網(wǎng)站建設(shè)公司_創(chuàng)新互聯(lián),為您提供響應(yīng)式網(wǎng)站、做網(wǎng)站、網(wǎng)站改版、關(guān)鍵詞優(yōu)化、定制開發(fā)、面包屑導(dǎo)航

廣告

聲明:本網(wǎng)站發(fā)布的內(nèi)容(圖片、視頻和文字)以用戶投稿、用戶轉(zhuǎn)載內(nèi)容為主,如果涉及侵權(quán)請(qǐng)盡快告知,我們將會(huì)在第一時(shí)間刪除。文章觀點(diǎn)不代表本網(wǎng)站立場(chǎng),如需處理請(qǐng)聯(lián)系客服。電話:028-86922220;郵箱:631063699@qq.com。內(nèi)容未經(jīng)允許不得轉(zhuǎn)載,或轉(zhuǎn)載時(shí)需注明來(lái)源: 創(chuàng)新互聯(lián)

微信小程序開發(fā)
聂拉木县| 陵川县| 平阳县| 尼玛县| 昌都县| 讷河市| 陕西省| 峡江县| 阳信县| 巴彦淖尔市| 承德市| 弥渡县| 洪江市| 建平县| 蒙阴县| 施秉县| 唐海县| 息烽县| 宁陵县| 福泉市| 苍溪县| 两当县| 封丘县| 泗洪县| 名山县| 肥城市| 迁安市| 丰宁| 沁源县| 拜城县| 独山县| 太康县| 荔浦县| 平安县| 华池县| 黑龙江省| 砚山县| 龙南县| 探索| 年辖:市辖区| 天全县|