<output id="qn6qe"></output>

    1. <output id="qn6qe"><tt id="qn6qe"></tt></output>
    2. <strike id="qn6qe"></strike>

      亚洲 日本 欧洲 欧美 视频,日韩中文字幕有码av,一本一道av中文字幕无码,国产线播放免费人成视频播放,人妻少妇偷人无码视频,日夜啪啪一区二区三区,国产尤物精品自在拍视频首页,久热这里只有精品12

      volatile關(guān)鍵字到底有什么作用

      提示:更多優(yōu)秀博文請移步博主的GitHub倉庫:GitHub學(xué)習(xí)筆記Gitee學(xué)習(xí)筆記

      volatile是Java提供的一種輕量級的同步機制。Java 語言包含兩種內(nèi)在的同步機制:同步塊(或方法)和 volatile 變量,相比于synchronized(synchronized通常稱為重量級鎖),volatile更輕量級,因為它不會引起線程上下文的切換和調(diào)度。但是volatile 變量的同步性較差(有時它更簡單并且開銷更低),而且其使用也更容易出錯。

      1. 并發(fā)編程的三個基本概念

      1.1 原子性

      定義: 即一個操作或者多個操作 要么全部執(zhí)行并且執(zhí)行的過程不會被任何因素打斷,要么就都不執(zhí)行。

      原子性是拒絕多線程操作的,不論是多核還是單核,具有原子性的量,同一時刻只能有一個線程來對它進行操作。簡而言之,在整個操作過程中不會被線程調(diào)度器中斷的操作,都可認(rèn)為是原子性。例如 a=1是原子性操作,但是a++和a +=1就不是原子性操作。

      Java中的原子性操作包括

      • 基本類型的讀取和賦值操作,且賦值必須是數(shù)字賦值給變量,變量之間的相互賦值不是原子性操作。
      • 所有引用reference的賦值操作
      • java.concurrent.Atomic.* 包中所有類的一切操作

      1.2 可見性

      定義:指當(dāng)多個線程訪問同一個變量時,一個線程修改了這個變量的值,其他線程能夠立即看得到修改的值。

      在多線程環(huán)境下,一個線程對共享變量的操作對其他線程是不可見的。Java提供了volatile來保證可見性,當(dāng)一個變量被volatile修飾后,表示著線程本地內(nèi)存無效,當(dāng)一個線程修改共享變量后他會立即被更新到主內(nèi)存中,其他線程讀取共享變量時,會直接從主內(nèi)存中讀取。當(dāng)然,synchronize和Lock都可以保證可見性。synchronized和Lock能保證同一時刻只有一個線程獲取鎖然后執(zhí)行同步代碼,并且在釋放鎖之前會將對變量的修改刷新到主存當(dāng)中。因此可以保證可見性。

      1.3 有序性

      定義:即程序執(zhí)行的順序按照代碼的先后順序執(zhí)行。

      Java內(nèi)存模型中的有序性可以總結(jié)為:如果在本線程內(nèi)觀察,所有操作都是有序的;如果在一個線程中觀察另一個線程,所有操作都是無序的。前半句是指“線程內(nèi)表現(xiàn)為串行語義”,后半句是指“指令重排序”現(xiàn)象和“工作內(nèi)存主主內(nèi)存同步延遲”現(xiàn)象。

      在Java內(nèi)存模型中,為了效率是允許編譯器和處理器對指令進行重排序,當(dāng)然重排序不會影響單線程的運行結(jié)果,但是對多線程會有影響。Java提供volatile來保證一定的有序性。最著名的例子就是單例模式里面的DCL(雙重檢查鎖)。另外,可以通過synchronized和Lock來保證有序性,synchronized和Lock保證每個時刻是有一個線程執(zhí)行同步代碼,相當(dāng)于是讓線程順序執(zhí)行同步代碼,自然就保證了有序性。

      2. Java內(nèi)存模型以及共享變量的可見性

      JMM決定一個線程對共享變量的寫入何時對另一個線程可見,JMM定義了線程和主內(nèi)存之間的抽象關(guān)系:共享變量存儲在主內(nèi)存(Main Memory)中,每個線程都有一個私有的本地內(nèi)存(Local Memory,有些文章將其稱為工作內(nèi)存),本地內(nèi)存保存了被該線程使用到的主內(nèi)存的副本拷貝,線程對變量的所有操作都必須在工作內(nèi)存中進行,而不能直接讀寫主內(nèi)存中的變量。

      在這里插入圖片描述
      對于普通的共享變量來講,線程A將其修改為某個值發(fā)生在線程A的本地內(nèi)存中,此時還未同步到主內(nèi)存中去;而線程B已經(jīng)緩存了該變量的舊值,所以就導(dǎo)致了共享變量值的不一致。解決這種共享變量在多線程模型中的不可見性問題,較粗暴的方式自然就是加鎖,但是此處使用synchronized或者Lock這些方式太重量級了,比較合理的方式其實就是volatile。

      需要注意的是,JMM是個抽象的內(nèi)存模型,所以所謂的本地內(nèi)存,主內(nèi)存都是抽象概念,并不一定就真實的對應(yīng)cpu緩存和物理內(nèi)存

      3. 鎖的互斥與可見性

      鎖提供了兩種主要特性:互斥(mutual exclusion) 和可見性(visibility)。

      1. 互斥即一次只允許一個線程持有某個特定的鎖,一次就只有一個線程能夠使用該共享數(shù)據(jù)。
      2. 可見性要更加復(fù)雜一些,它必須確保釋放鎖之前對共享數(shù)據(jù)做出的更改對于隨后獲得該鎖的另一個線程是可見的。也即當(dāng)一條線程修改了共享變量的值,新值對于其他線程來說是可以立即得知的。如果沒有同步機制提供的這種可見性保證,線程看到的共享變量可能是修改前的值或不一致的值,這將引發(fā)許多嚴(yán)重問題。

      4. volatile變量的特性

      4.1 保證可見性,不保證原子性

      • 可見性:當(dāng)某個線程修改volatile變量時,JMM會強制將這個修改更新到主內(nèi)存中,并且讓其他線程工作內(nèi)存中存儲的副本失效。
      • volatile變量并不能保證其操作的原子性,具體來說像i++這種操作并不是原子操作,使用volatile修飾變量后仍然不能保證這一點。具體體現(xiàn)我們看下面的代碼:
      public class Test {
          private static volatile int count = 0;
      
          public static void main(String[] args){
              Thread[] threads = new Thread[5];
              for(int i = 0; i<5; i++){
                  threads[i] = new Thread(()->{
                      try{
                          for(int j = 0; j<10; j++){
                              System.out.println(++count);
                              Thread.sleep(500);
                          }
                      }catch (Exception e){
                          e.printStackTrace();
                      }
                  });
                  threads[i].start();
              }
          }
      }
      

      生成5條線程,每條線程都對count執(zhí)行10次自增操作,我們預(yù)期結(jié)果是1-50均勻的打印出來,但不管運行多少次,都無法得到期望的結(jié)果:
      在這里插入圖片描述
      這說明僅僅保證數(shù)據(jù)的可見性并不能保證線程安全,具體原因我們來分析一下:

      首先我們需要明確的是,count++操作并不是原子操作,因為自增操作包括三個基本指令:讀取數(shù)據(jù)、計算數(shù)據(jù)、返回結(jié)果,可以看看i++相關(guān)的字節(jié)碼:

      Code:
             0: getstatic	//讀取原數(shù)據(jù)
             3: iconst_1	//定義常量1
             4: iadd	//計算數(shù)據(jù)
             5: putstatic  //輸出結(jié)果                 // Field count:I
             8: return
      

      現(xiàn)象一:數(shù)據(jù)沒有按順序輸出

      假如線程A獲取執(zhí)行權(quán),并在“返回結(jié)果”后停止(未打印),而轉(zhuǎn)為線程B執(zhí)行操作,巧合的是線程B這三步操作在一個時間片中完成:讀取數(shù)據(jù)、計算數(shù)據(jù)、返回結(jié)果、打印數(shù)據(jù),然后時間片轉(zhuǎn)回線程A,線程打印剛剛計算的數(shù)據(jù),此時就會發(fā)生先打印的數(shù)據(jù)比后打印的數(shù)據(jù)大的問題。

      現(xiàn)象二:數(shù)據(jù)輸出重復(fù)

      假如線程A獲取執(zhí)行權(quán),并在“讀取數(shù)據(jù)”后停止;線程B開始執(zhí)行,由于線程A還沒有進行數(shù)據(jù)計算和數(shù)據(jù)返回操作,也就是說主內(nèi)存中的數(shù)據(jù)并沒有更新,而此時B線程完成自增操作,并且輸出結(jié)果。轉(zhuǎn)回線程A,由于它已經(jīng)完成了數(shù)據(jù)讀取工作,它將繼續(xù)往下執(zhí)行,但是它現(xiàn)在讀取的數(shù)據(jù)已經(jīng)是一個過期數(shù)據(jù)了,最終輸出結(jié)果會和B線程輸出的一樣。

      所以保證數(shù)據(jù)可見性并不能保證線程安全,事實上就是保證操作是原子性操作,才能保證使用volatile關(guān)鍵字的程序在并發(fā)時能夠正確執(zhí)行。而鎖機制剛好能保證操作的原子性和可見性。而鎖機制之所以能保證原子性,是因為鎖有互斥性,并且對于一個已經(jīng)競爭到同步鎖的線程,在還沒有走出同步塊的時候,即使時間片結(jié)束也不會釋放鎖。

      實事求是的說,筆者在此使用字節(jié)碼來分析問題,仍然不夠嚴(yán)謹(jǐn),因為即使編譯出來的字節(jié)碼只有一條指令,也并不意味著執(zhí)行是一個原子操作。一條字節(jié)碼指令在解釋執(zhí)行時,解釋器將要運行多行代碼才能實現(xiàn)它的語義。但是在這里通過字節(jié)碼就能夠說明自增操作不是原子操作,所以此處用字節(jié)碼進行分析。

      4.2 禁止指令重排

      指令重排是指JVM在編譯Java代碼的時候,或者CPU在執(zhí)行JVM字節(jié)碼的時候,對現(xiàn)有的指令順序進行重新排序

      指令重排的目的是為了在不改變程序執(zhí)行結(jié)果的前提下,優(yōu)化程序的運行效率。需要注意的是,這里所說的不改變執(zhí)行結(jié)果,指的是不改變單線程下的程序執(zhí)行結(jié)果。

      重排序操作不會對存在數(shù)據(jù)依賴關(guān)系的操作進行重排序。比如:a=1;b=a; 這個指令序列,由于第二個操作依賴于第一個操作,所以在編譯時和處理器運時這兩個操作不會被重排序。

      重排序是為了優(yōu)化性能,但是不管怎么重排序,單線程下程序的執(zhí)行結(jié)果不能被改變。比如:a=1;b=2;c=a+b這三個操作,第一步(a=1)和第二步(b=2)由于不存在數(shù)據(jù)依賴關(guān)系, 所以可能會發(fā)生重排序,但是c=a+b這個操作是不會被重排序的,因為需要保證最終的結(jié)果一定是c=a+b=3。

      然而,指令重排是一把雙刃劍,重排序在單線程下一定能保證結(jié)果的正確性,但是在多線程環(huán)境下,可能發(fā)生重排序,影響結(jié)果,下例中的1和2由于不存在數(shù)據(jù)依賴關(guān)系,則有可能會被重排序,先執(zhí)行status=true再執(zhí)行a=2。而此時線程B會順利到達4處,而線程A中a=2這個操作還未被執(zhí)行,所以b=a+1的結(jié)果也有可能依然等于2。

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

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

      1. 當(dāng)程序執(zhí)行到volatile變量的讀操作或者寫操作時,在其前面的操作的更改肯定全部已經(jīng)進行,且結(jié)果已經(jīng)對后面的操作可見;在其后面的操作肯定還沒有進行;
      2. 在進行指令優(yōu)化時,不能將在對volatile變量訪問的語句放在其后面執(zhí)行,也不能把volatile變量后面的語句放到其前面執(zhí)行。

      通俗的說就是執(zhí)行到volatile變量時,其前面的所有語句都執(zhí)行完,后面所有語句都未執(zhí)行。且前面語句的結(jié)果對volatile變量及其后面語句可見

      5. volatile的原理

      volatile可以保證線程可見性且提供了一定的有序性,但是無法保證原子性。在JVM底層volatile是采用“內(nèi)存屏障”來實現(xiàn)的。觀察加入volatile關(guān)鍵字和沒有加入volatile關(guān)鍵字時所生成的匯編代碼發(fā)現(xiàn),加入volatile關(guān)鍵字時,會多出一個lock前綴指令,lock前綴指令實際上相當(dāng)于一個內(nèi)存屏障(也成內(nèi)存柵欄),內(nèi)存屏障會提供3個功能:

      1. 它確保指令重排序時不會把其后面的指令排到內(nèi)存屏障之前的位置,也不會把前面的指令排到內(nèi)存屏障的后面;即在執(zhí)行到內(nèi)存屏障這句指令時,在它前面的操作已經(jīng)全部完成;
      2. 它會強制將對緩存的修改操作立即寫入主存;
      3. 如果是寫操作,它會導(dǎo)致其他CPU中對應(yīng)的緩存行無效。

      6. 單例模式的雙重鎖為什么要加volatile

      [外鏈圖片轉(zhuǎn)存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-ocKJ6ro3-1585273171038)(…/images/6.png)]

      需要volatile關(guān)鍵字的原因是,在并發(fā)情況下,如果沒有volatile關(guān)鍵字,在第5行會出現(xiàn)問題。instance = new TestInstance();可以分解為3行偽代碼:

      • memory = allocate() //分配內(nèi)存
      • ctorInstanc(memory) //初始化對象
      • instance = memory //設(shè)置instance指向剛分配的地址

      上面的代碼在編譯運行時,可能會出現(xiàn)重排序從a-b-c排序為a-c-b。在多線程的情況下會出現(xiàn)以下問題。當(dāng)線程A在執(zhí)行第5行代碼時,B線程進來執(zhí)行到第2行代碼。假設(shè)此時A執(zhí)行的過程中發(fā)生了指令重排序,即先執(zhí)行了a和c,沒有執(zhí)行b。那么由于A線程執(zhí)行了c導(dǎo)致instance指向了一段地址,所以B線程判斷instance不為null,會直接跳到第6行并返回一個未初始化的對象。(synchronized并不能禁止指令重排和處理器優(yōu)化)

      拓展:雙鎖校驗的優(yōu)勢在于在大多數(shù)情況下,不需要進入synchronized的代碼塊中,大大加快了程序的執(zhí)行效率,而如果在第一次執(zhí)行g(shù)etInstance()方法時,也能保證對象創(chuàng)建的安全性。

      參考文章

      • https://blog.csdn.net/u012723673/article/details/80682208

      • https://mp.weixin.qq.com/s?__biz=MzIxMjE5MTE1Nw==&mid=2653192450&idx=2&sn=ad95717051c0c4af83923b736a5bc637&chksm=8c99f3d8bbee7aceb123e4f6aa9a220630b5aa17743ba812d82308bfb6a8ed8303bdd181f144&mpshare=1&scene=23&srcid=0929Elncx0R0l02WWFrc9Cpt&sharer_sharetime=1569734533458&sharer_shareid=e81601a95b901aeca142bbe3b957819a#rd

      posted @ 2020-03-27 09:42  聽到微笑  閱讀(11)  評論(0)    收藏  舉報  來源
      主站蜘蛛池模板: 成年在线观看免费人视频| 中文字幕精品无码一区二区| 亚洲av无一区二区三区| 日本一区二区精品色超碰| 日韩有码国产精品一区| 日韩丝袜亚洲国产欧美一区| 中文字幕结果国产精品| 精品人妻人人做人人爽夜夜爽| 毛片av在线尤物一区二区 | 隔壁老王国产在线精品| 韩国19禁无遮挡啪啪无码网站 | 亚洲国产欧美在线观看片| 久久精品夜夜夜夜夜久久| 南昌市| 国产美女久久精品香蕉| 人妻中文字幕不卡精品| 亚洲国产欧美在线人成| 老师破女学生处特级毛ooo片| 蜜臀av入口一区二区三区| 国产精品自在欧美一区| 色香欲天天影视综合网| 啊轻点灬大JI巴太粗太长了在线| 久久精品不卡一区二区| 国自产拍偷拍精品啪啪模特| 国产伦一区二区三区久久| 午夜福利在线观看6080| 中文字幕乱码在线人视频| 性欧美乱熟妇xxxx白浆| 亚洲情色av一区二区| 国产av一区二区午夜福利| 亚州中文字幕一区二区| 国产精品日本一区二区不卡视频| 青青热在线精品视频免费观看| 东京热人妻无码一区二区av| 欧美乱大交aaaa片if| 国内精品伊人久久久久AV一坑| 熟妇人妻无码中文字幕老熟妇| 婷婷丁香五月亚洲中文字幕| 无码人妻精品一区二区三区下载| 丰满少妇被猛烈进出69影院| 99riav精品免费视频观看|