最新国产好看的视频,伊人天堂AV在线,国产Aaaaaa视频,蜜臀视频在线观看一区,人妻av色图,密臀久久久精品影片,青青视频免费观看毛片,久草在线观看视,国产三级精品色情在线

深入淺析Java中的volatile

 更新時(shí)間:2017年03月19日 09:50:33   作者:dreamcatcher-cx  
volatile是Java提供的一種輕量級(jí)的同步機(jī)制,在并發(fā)編程中,它也扮演著比較重要的角色.這篇文章主要介紹了深入淺析Java中的volatile,需要的朋友可以參考下

內(nèi)存可見(jiàn)性

  volatile是Java提供的一種輕量級(jí)的同步機(jī)制,在并發(fā)編程中,它也扮演著比較重要的角色。同synchronized相比(synchronized通常稱(chēng)為重量級(jí)鎖),volatile更輕量級(jí),相比使用synchronized時(shí)引起的線(xiàn)程上下文切換所帶來(lái)的龐大開(kāi)銷(xiāo),倘若能恰當(dāng)?shù)暮侠淼氖褂胿olatile,自然是美事一樁。

  為了能比較清晰徹底的理解volatile,我們一步一步來(lái)分析。首先來(lái)看看如下代碼

public class TestVolatile {
 boolean status = false;
 /**
  * 狀態(tài)切換為true
  */
 public void changeStatus(){
  status = true;
 }
 /**
  * 若狀態(tài)為true,則running。
  */
 public void run(){
  if(status){
   System.out.println("running....");
  }
 }
}

  上面這個(gè)例子,在多線(xiàn)程環(huán)境里,假設(shè)線(xiàn)程A執(zhí)行changeStatus()方法后,線(xiàn)程B運(yùn)行run()方法,可以保證輸出"running....."嗎?

  答案是NO!

  這個(gè)結(jié)論會(huì)讓人有些疑惑,可以理解。因?yàn)樘热粼趩尉€(xiàn)程模型里,先運(yùn)行changeStatus方法,再執(zhí)行run方法,自然是可以正確輸出"running...."的;但是在多線(xiàn)程模型中,是沒(méi)法做這種保證的。因?yàn)閷?duì)于共享變量status來(lái)說(shuō),線(xiàn)程A的修改,對(duì)于線(xiàn)程B來(lái)講,是"不可見(jiàn)"的。也就是說(shuō),線(xiàn)程B此時(shí)可能無(wú)法觀測(cè)到status已被修改為true。那么什么是可見(jiàn)性呢?

  所謂可見(jiàn)性,是指當(dāng)一條線(xiàn)程修改了共享變量的值,新值對(duì)于其他線(xiàn)程來(lái)說(shuō)是可以立即得知的。很顯然,上述的例子中是沒(méi)有辦法做到內(nèi)存可見(jiàn)性的。

  Java內(nèi)存模型

  為什么出現(xiàn)這種情況呢,我們需要先了解一下JMM(java內(nèi)存模型)

  java虛擬機(jī)有自己的內(nèi)存模型(Java Memory Model,JMM),JMM可以屏蔽掉各種硬件和操作系統(tǒng)的內(nèi)存訪問(wèn)差異,以實(shí)現(xiàn)讓java程序在各種平臺(tái)下都能達(dá)到一致的內(nèi)存訪問(wèn)效果。

  JMM決定一個(gè)線(xiàn)程對(duì)共享變量的寫(xiě)入何時(shí)對(duì)另一個(gè)線(xiàn)程可見(jiàn),JMM定義了線(xiàn)程和主內(nèi)存之間的抽象關(guān)系:共享變量存儲(chǔ)在主內(nèi)存(Main Memory)中,每個(gè)線(xiàn)程都有一個(gè)私有的本地內(nèi)存(Local Memory),本地內(nèi)存保存了被該線(xiàn)程使用到的主內(nèi)存的副本拷貝,線(xiàn)程對(duì)變量的所有操作都必須在工作內(nèi)存中進(jìn)行,而不能直接讀寫(xiě)主內(nèi)存中的變量。這三者之間的交互關(guān)系如下

 

  需要注意的是,JMM是個(gè)抽象的內(nèi)存模型,所以所謂的本地內(nèi)存,主內(nèi)存都是抽象概念,并不一定就真實(shí)的對(duì)應(yīng)cpu緩存和物理內(nèi)存。當(dāng)然如果是出于理解的目的,這樣對(duì)應(yīng)起來(lái)也無(wú)不可。

  大概了解了JMM的簡(jiǎn)單定義后,問(wèn)題就很容易理解了,對(duì)于普通的共享變量來(lái)講,比如我們上文中的status,線(xiàn)程A將其修改為true這個(gè)動(dòng)作發(fā)生在線(xiàn)程A的本地內(nèi)存中,此時(shí)還未同步到主內(nèi)存中去;而線(xiàn)程B緩存了status的初始值false,此時(shí)可能沒(méi)有觀測(cè)到status的值被修改了,所以就導(dǎo)致了上述的問(wèn)題。那么這種共享變量在多線(xiàn)程模型中的不可見(jiàn)性如何解決呢?比較粗暴的方式自然就是加鎖,但是此處使用synchronized或者Lock這些方式太重量級(jí)了,有點(diǎn)炮打蚊子的意思。比較合理的方式其實(shí)就是volatile

  volatile具備兩種特性,第一就是保證共享變量對(duì)所有線(xiàn)程的可見(jiàn)性。將一個(gè)共享變量聲明為volatile后,會(huì)有以下效應(yīng):

    1.當(dāng)寫(xiě)一個(gè)volatile變量時(shí),JMM會(huì)把該線(xiàn)程對(duì)應(yīng)的本地內(nèi)存中的變量強(qiáng)制刷新到主內(nèi)存中去;

    2.這個(gè)寫(xiě)會(huì)操作會(huì)導(dǎo)致其他線(xiàn)程中的緩存無(wú)效。

上面的例子只需將status聲明為volatile,即可保證在線(xiàn)程A將其修改為true時(shí),線(xiàn)程B可以立刻得知

 volatile boolean status = false;

留意復(fù)合類(lèi)操作

  但是需要注意的是,我們一直在拿volatile和synchronized做對(duì)比,僅僅是因?yàn)檫@兩個(gè)關(guān)鍵字在某些內(nèi)存語(yǔ)義上有共通之處,volatile并不能完全替代synchronized,它依然是個(gè)輕量級(jí)鎖,在很多場(chǎng)景下,volatile并不能勝任??聪逻@個(gè)例子:

package test;
import java.util.concurrent.CountDownLatch;
/**
 * Created by chengxiao on 2017/3/18.
 */
public class Counter {
 public static volatile int num = 0;
 //使用CountDownLatch來(lái)等待計(jì)算線(xiàn)程執(zhí)行完
 static CountDownLatch countDownLatch = new CountDownLatch(30);
 public static void main(String []args) throws InterruptedException {
  //開(kāi)啟30個(gè)線(xiàn)程進(jìn)行累加操作
  for(int i=0;i<30;i++){
   new Thread(){
    public void run(){
     for(int j=0;j<10000;j++){
      num++;//自加操作
     }
     countDownLatch.countDown();
    }
   }.start();
  }
  //等待計(jì)算線(xiàn)程執(zhí)行完
  countDownLatch.await();
  System.out.println(num);
 }
}

執(zhí)行結(jié)果:

224291

針對(duì)這個(gè)示例,一些同學(xué)可能會(huì)覺(jué)得疑惑,如果用volatile修飾的共享變量可以保證可見(jiàn)性,那么結(jié)果不應(yīng)該是300000么?

問(wèn)題就出在num++這個(gè)操作上,因?yàn)閚um++不是個(gè)原子性的操作,而是個(gè)復(fù)合操作。我們可以簡(jiǎn)單講這個(gè)操作理解為由這三步組成:

  1.讀取

  2.加一

  3.賦值

  所以,在多線(xiàn)程環(huán)境下,有可能線(xiàn)程A將num讀取到本地內(nèi)存中,此時(shí)其他線(xiàn)程可能已經(jīng)將num增大了很多,線(xiàn)程A依然對(duì)過(guò)期的num進(jìn)行自加,重新寫(xiě)到主存中,最終導(dǎo)致了num的結(jié)果不合預(yù)期,而是小于30000。

解決num++操作的原子性問(wèn)題

  針對(duì)num++這類(lèi)復(fù)合類(lèi)的操作,可以使用java并發(fā)包中的原子操作類(lèi)原子操作類(lèi)是通過(guò)循環(huán)CAS的方式來(lái)保證其原子性的。

/**
 * Created by chengxiao on 2017/3/18.
 */
public class Counter {
  //使用原子操作類(lèi)
 public static AtomicInteger num = new AtomicInteger(0);
 //使用CountDownLatch來(lái)等待計(jì)算線(xiàn)程執(zhí)行完
 static CountDownLatch countDownLatch = new CountDownLatch(30);
 public static void main(String []args) throws InterruptedException {
  //開(kāi)啟30個(gè)線(xiàn)程進(jìn)行累加操作
  for(int i=0;i<30;i++){
   new Thread(){
    public void run(){
     for(int j=0;j<10000;j++){
      num.incrementAndGet();//原子性的num++,通過(guò)循環(huán)CAS方式
     }
     countDownLatch.countDown();
    }
   }.start();
  }
  //等待計(jì)算線(xiàn)程執(zhí)行完
  countDownLatch.await();
  System.out.println(num);
 }
}

執(zhí)行結(jié)果

300000
關(guān)于原子類(lèi)操作的基本原理,會(huì)在后面的章節(jié)進(jìn)行介紹,此處不再贅述。

禁止指令重排序

volatile還有一個(gè)特性:禁止指令重排序優(yōu)化。

重排序是指編譯器和處理器為了優(yōu)化程序性能而對(duì)指令序列進(jìn)行排序的一種手段。但是重排序也需要遵守一定規(guī)則:

  1.重排序操作不會(huì)對(duì)存在數(shù)據(jù)依賴(lài)關(guān)系的操作進(jìn)行重排序。

    比如:a=1;b=a; 這個(gè)指令序列,由于第二個(gè)操作依賴(lài)于第一個(gè)操作,所以在編譯時(shí)和處理器運(yùn)行時(shí)這兩個(gè)操作不會(huì)被重排序。

  2.重排序是為了優(yōu)化性能,但是不管怎么重排序,單線(xiàn)程下程序的執(zhí)行結(jié)果不能被改變

    比如:a=1;b=2;c=a+b這三個(gè)操作,第一步(a=1)和第二步(b=2)由于不存在數(shù)據(jù)依賴(lài)關(guān)系,所以可能會(huì)發(fā)生重排序,但是c=a+b這個(gè)操作是不會(huì)被重排序的,因?yàn)樾枰WC最終的結(jié)果一定是c=a+b=3。

  重排序在單線(xiàn)程模式下是一定會(huì)保證最終結(jié)果的正確性,但是在多線(xiàn)程環(huán)境下,問(wèn)題就出來(lái)了,來(lái)開(kāi)個(gè)例子,我們對(duì)第一個(gè)TestVolatile的例子稍稍改進(jìn),再增加個(gè)共享變量a

public class TestVolatile {
 int a = 1;
 boolean status = false;
 /**
  * 狀態(tài)切換為true
  */
 public void changeStatus(){
  a = 2;//1
  status = true;//2
 }
 /**
  * 若狀態(tài)為true,則running。
  */
 public void run(){
  if(status){//3
   int b = a+1;//4
   System.out.println(b);
  }
 }
}

  假設(shè)線(xiàn)程A執(zhí)行changeStatus后,線(xiàn)程B執(zhí)行run,我們能保證在4處,b一定等于3么?

  答案依然是無(wú)法保證!也有可能b仍然為2。上面我們提到過(guò),為了提供程序并行度,編譯器和處理器可能會(huì)對(duì)指令進(jìn)行重排序,而上例中的1和2由于不存在數(shù)據(jù)依賴(lài)關(guān)系,則有可能會(huì)被重排序,先執(zhí)行status=true再執(zhí)行a=2。而此時(shí)線(xiàn)程B會(huì)順利到達(dá)4處,而線(xiàn)程A中a=2這個(gè)操作還未被執(zhí)行,所以b=a+1的結(jié)果也有可能依然等于2。

  使用volatile關(guān)鍵字修飾共享變量便可以禁止這種重排序。若用volatile修飾共享變量,在編譯時(shí),會(huì)在指令序列中插入內(nèi)存屏障來(lái)禁止特定類(lèi)型的處理器重排序

  volatile禁止指令重排序也有一些規(guī)則,簡(jiǎn)單列舉一下:

  1.當(dāng)?shù)诙€(gè)操作是voaltile寫(xiě)時(shí),無(wú)論第一個(gè)操作是什么,都不能進(jìn)行重排序

  2.當(dāng)?shù)匾粋€(gè)操作是volatile讀時(shí),不管第二個(gè)操作是什么,都不能進(jìn)行重排序

  3.當(dāng)?shù)谝粋€(gè)操作是volatile寫(xiě)時(shí),第二個(gè)操作是volatile讀時(shí),不能進(jìn)行重排序

總結(jié):

  簡(jiǎn)單總結(jié)下,volatile是一種輕量級(jí)的同步機(jī)制,它主要有兩個(gè)特性:一是保證共享變量對(duì)所有線(xiàn)程的可見(jiàn)性;二是禁止指令重排序優(yōu)化。同時(shí)需要注意的是,volatile對(duì)于單個(gè)的共享變量的讀/寫(xiě)具有原子性,但是像num++這種復(fù)合操作,volatile無(wú)法保證其原子性,當(dāng)然文中也提出了解決方案,就是使用并發(fā)包中的原子操作類(lèi),通過(guò)循環(huán)CAS地方式來(lái)保證num++操作的原子性。關(guān)于原子操作類(lèi),會(huì)在后續(xù)的文章進(jìn)行介紹。大家敬請(qǐng)關(guān)注腳本之家網(wǎng)站!

相關(guān)文章

  • Java增強(qiáng)for循環(huán)的增刪操作代碼

    Java增強(qiáng)for循環(huán)的增刪操作代碼

    Foreach循環(huán)(Foreach loop)是計(jì)算機(jī)編程語(yǔ)言中的一種控制流程語(yǔ)句,通常用來(lái)循環(huán)遍歷數(shù)組或集合中的元素,本文通過(guò)實(shí)例演示普通for循環(huán)和foreach循環(huán)使用,java增強(qiáng)for循環(huán)的操作代碼感興趣的朋友一起看看吧
    2024-02-02
  • MyEclipse 2016 CI 4新增BootStrap模板

    MyEclipse 2016 CI 4新增BootStrap模板

    MyEclipse2016是一款全球使用最為廣泛的企業(yè)級(jí)開(kāi)發(fā)環(huán)境程序,這篇文章主要介紹了MyEclipse 2016 CI 4新增BootStrap模板的相關(guān)資料,非常不錯(cuò),具有參考借鑒價(jià)值,需要的朋友可以參考下
    2016-06-06
  • java使用jna調(diào)用c#中dll的方法詳解

    java使用jna調(diào)用c#中dll的方法詳解

    前一段時(shí)間接了個(gè)項(xiàng)目,需要用到第三方提供的C#編寫(xiě)的dll,本身項(xiàng)目是java語(yǔ)言,所以便有了下面這篇文章,本文給大家介紹了關(guān)于java中如何使用jna調(diào)用c#中dll的相關(guān)資料,文中通過(guò)示例代碼介紹的非常詳細(xì),需要的朋友可以參考借鑒,下面來(lái)一起看看吧。
    2017-09-09
  • Java實(shí)現(xiàn)全圖背景水印的示例詳解

    Java實(shí)現(xiàn)全圖背景水印的示例詳解

    這篇文章主要為大家詳細(xì)介紹了如何利用Java實(shí)現(xiàn)全圖背景水印的方法,文中的示例代碼講解詳細(xì),具有一定的借鑒價(jià)值,需要的可以參考一下
    2023-02-02
  • Spring Cloud Eureka 服務(wù)上下線(xiàn)監(jiān)控的實(shí)現(xiàn)

    Spring Cloud Eureka 服務(wù)上下線(xiàn)監(jiān)控的實(shí)現(xiàn)

    這篇文章主要介紹了Spring Cloud Eureka 服務(wù)上下線(xiàn)監(jiān)控的實(shí)現(xiàn),小編覺(jué)得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧
    2018-09-09
  • Mybatis Plus插件三種方式的逆向工程的使用

    Mybatis Plus插件三種方式的逆向工程的使用

    這篇文章主要介紹了Mybatis Plus插件三種方式的逆向工程的使用,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧
    2020-11-11
  • JProfiler11使用教程之JVM調(diào)優(yōu)問(wèn)題小結(jié)

    JProfiler11使用教程之JVM調(diào)優(yōu)問(wèn)題小結(jié)

    這篇文章主要介紹了JProfiler11使用教程之JVM調(diào)優(yōu),本文通過(guò)圖文并茂的形式給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下
    2022-03-03
  • Java中三種零拷貝的實(shí)現(xiàn)示例以及對(duì)比詳解

    Java中三種零拷貝的實(shí)現(xiàn)示例以及對(duì)比詳解

    這篇文章主要介紹了Java中三種零拷貝的實(shí)現(xiàn)示例以及對(duì)比詳解,本文主要是介紹幾種零拷貝的實(shí)現(xiàn)示例,以及與最傳統(tǒng)的做一個(gè)對(duì)比,看看在效率上到底有多大的提升,需要的朋友可以參考下
    2023-12-12
  • IDEA如何開(kāi)啟并配置services窗口

    IDEA如何開(kāi)啟并配置services窗口

    在使用IntelliJ IDEA時(shí),可能會(huì)遇到Services窗口不自動(dòng)彈出的情況,本文介紹了如何手動(dòng)開(kāi)啟Services窗口的簡(jiǎn)單步驟,首先,通過(guò)點(diǎn)擊菜單欄中的“視圖”->“工具窗口”->“服務(wù)”,或使用快捷鍵Alt+F8(注意快捷鍵可能存在沖突)來(lái)打開(kāi)Services窗口
    2024-10-10
  • Java 基礎(chǔ)詳解(泛型、集合、IO、反射)

    Java 基礎(chǔ)詳解(泛型、集合、IO、反射)

    下面小編就為大家?guī)?lái)一篇Java 基礎(chǔ)詳解(泛型、集合、IO、反射)。小編覺(jué)得挺不錯(cuò)的,現(xiàn)在就分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧
    2017-10-10

最新評(píng)論

家居| 资溪县| 翁牛特旗| 梨树县| 越西县| 邻水| 金坛市| 抚顺县| 呼伦贝尔市| 荣成市| 钦州市| 垫江县| 阆中市| 沂南县| 土默特左旗| 阿拉善右旗| 内乡县| 嘉祥县| 东兴市| 大丰市| 阿图什市| 永清县| 西乡县| 泊头市| 页游| 福泉市| 宁化县| 平乡县| 洪雅县| 固安县| 菏泽市| 边坝县| 靖宇县| 鱼台县| 丘北县| 鹤壁市| 富顺县| 鸡泽县| 甘南县| 玛纳斯县| 七台河市|