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

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

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

      你說一下對Java中的volatile的理解吧

      前言

      volatile相關的知識其實自己一直都是有掌握的,能大概講出一些知識,例如:它可以保證可見性禁止指令重排。這兩個特性張口就來,但要再往深了問,具體是如何實現(xiàn)這兩個特性的,以及在什么場景下使用volatile,為什么不直接用synchronized這種深入和擴展相關的問題,就回答的不好了。因為volatile是面試必問的知識,所以這次準備把這部分知識也給啃掉。

      系統(tǒng)處理效率與Java內(nèi)存模型

      在計算機中,每條程序指令都是在CPU中執(zhí)行的,而CPU執(zhí)行指令的數(shù)據(jù)都是臨時存儲在內(nèi)存中的,但是CPU的執(zhí)行速度遠超內(nèi)存的讀取速度,如果所有的CPU指令都是通過內(nèi)存來讀取數(shù)據(jù)的話那么將大大的降低了系統(tǒng)的處理效率,所以現(xiàn)代計算機系統(tǒng)都不得不加入一層或多層讀寫速度盡可能接近處理器運算速度的高速緩存(Cache)來作為內(nèi)存與處理器之間的緩沖

      將運算需要使用的數(shù)據(jù)復制到緩存中,讓運算能快速進行,當運算結束后,在從緩存同步回內(nèi)存之中,這樣處理器就無須等待緩慢的內(nèi)存讀寫了。

      雖然說增加了高速緩存提高了CPU的處理效率,但是也帶來了新的問題 :

      現(xiàn)代計算機都是多核CPU,一開始,內(nèi)存中的變量A的值是1,第一個CPU讀取了數(shù)據(jù),第二個CPU也將數(shù)據(jù)讀取到了自己的高速緩存當中,當?shù)谝粋€CPU對變量A進行加1操作時,變量A的值變成了2,然后將將變量A的值寫回內(nèi)存中,這時第二個CPU也對變量A進行加1操作時,由于第二個CPU中高速緩存中的值還是1,所以加1操作后的結果為2,然后第二個CPU又將變量A的值同步回內(nèi)存中,這樣就導致執(zhí)行了兩次加1操作后,變量A的值最終是2,而不是3。
      這種被多個CPU訪問的變量,通常稱為共享變量。
      而產(chǎn)生的上面的問題,就是引入了高速緩存后的,主內(nèi)存和緩存內(nèi)容不一致的問題。
      因為每個處理器有自己的高速緩存,但是它們又共享同一塊主內(nèi)存,所以必然會出現(xiàn)主內(nèi)存不知該以哪個高速緩存中的變量為準的情況。
      在這里插入圖片描述
      上面這個緩存不一致的問題,我們先記下來,繼續(xù)來看Java內(nèi)存模型,其實Java內(nèi)存模型描述的上面講的計算機系統(tǒng)高速緩存和內(nèi)存之間的關系類似。

      Java內(nèi)存模型描述了,各種變量的訪問規(guī)則,以及將變量存儲到內(nèi)存和從內(nèi)存讀取變量的這種底層細節(jié)。

      在Java內(nèi)存模型中關注的變量都是共享變量(實例變量、類變量)。
      所有的共享變量都是存儲在主內(nèi)存中的,但是每個線程在訪問變量的時候也都會在自己的工作內(nèi)存處理器高速緩存)中保留一份共享變量的副本。

      Java內(nèi)存模型(Java Memory Model,簡稱JMM)規(guī)定:

      線程對變量的所有操作(讀,寫)都必須在工作內(nèi)存中進行,不能直接操作主內(nèi)存中的數(shù)據(jù)。
      不同線程之間 也不能直接訪問對方工作內(nèi)存中的變量,線程間的變量值傳遞必須通過主內(nèi)存進行中轉(zhuǎn)傳遞。
      在JMM中工作內(nèi)存和主內(nèi)存的關系如下圖:
      在這里插入圖片描述

      Volatile的可見性(保證立即可見)

      繼續(xù)我們上面的緩存一致性的問題,這個問題,在Java內(nèi)存模型中,就是可見性的問題,即一個線程修改了共享變量的值,對另一個線程來說是不是立即可見的。如果不是立即可見的,那么就會出現(xiàn)緩存一致性的問題,如果是立即可見的,那么另一個線程在進行操作的時候,拿到的變量值就是最新的。就可以解決可見性的問題。

      那么怎么解決可見性問題呢?

      • 方案一:加鎖

      將共享變量加鎖,無論是synchronized還是Lock都可以,加鎖達到的目的是在同一時間內(nèi)只能有一個線程能對共享變量進行操作,就是說,共享變量從讀取到工作內(nèi)存到更新值后,同步回主內(nèi)存的過程中,其他線程是操作不了這個變量的。這樣自然就解決了可見性的問題了,但是這樣的效率比較低,操作不了共享變量的線程就只能阻塞。

      • 方案二:volatile修飾共享變量

      當一個共享變量被volatile修飾后,會保證每個線程將變量修改后的值立即同步回主內(nèi)存中,當其他線程有需要讀取變量時會讀取到最新的變量值。

      那么volatile做了些什么操作就能解決可見性的問題呢?

      被volatile修飾的變量,在被線程操作時,會有這樣的機制:
      

      就是線程對變量操作時會從主內(nèi)存中讀取到自己的工作內(nèi)存中,當線程對變量進行了修改后,那么其他已經(jīng)讀取了此變量的線程中的變量副本就會失效,這樣其他線程在使用變量的時候,發(fā)現(xiàn)已經(jīng)失效,那么就會去主內(nèi)存中重新獲取,這樣獲取到的就只最新的值了。

      那么volatile這個關鍵字是如何實現(xiàn)這套機制的呢?

      因為一臺計算機有多臺CPU,同一個變量,在多個CPU中緩存的值有可能不一樣,那么以誰緩存的值為準呢?

      既然大家都有自己的值,那么各個CPU間就產(chǎn)生了一種協(xié)議,來保證按照一定的規(guī)律為準,來確定共享變量的準確值,這樣各個CPU在讀寫共享變量時都按照協(xié)議來操作。

      這就是緩存一致性協(xié)議。

      最著名的緩存一致性協(xié)議就是Intel的MESI了,說MESI時,先解釋一下,緩存行:

      緩存行(cache line):CPU高速緩存的中可以分配的最小存儲單位,高速緩存中的變量都是存在緩存行中的。

      MESI的核心思想就是,當CPU對變量進行寫操作時發(fā)現(xiàn),變量是共享變量,那么就會通知其他CPU中將該變量的緩存行設置為無效狀態(tài)。當其他CPU在操作變量時發(fā)現(xiàn)此變量在的緩存行已經(jīng)無效,那么就會去主內(nèi)存中重新讀取最新的變量。

      • 那么其他CPU是如何發(fā)現(xiàn)變量被修改了的呢?

      因為CPU和其他部件的進行通信是通過總線來進行的,所以每個CPU通過嗅探總線上的傳播數(shù)據(jù),來檢查自己緩存的值是不是過期了,當處理器發(fā)現(xiàn)自己緩存行對應的內(nèi)存地址被修改后,就會將自己工作內(nèi)存中的緩存行設置成無效狀態(tài),當CPU對此變量進行修改時會重新從系統(tǒng)主內(nèi)存中讀取變量。

      在這里插入圖片描述

      Volatile的有序性(禁止指令重排)

      一般來說,我們寫程序的時候,都是要把先代碼從上往下寫,默認的認為程序是自頂向下順序執(zhí)行的,但是CPU為了提高效率,在保證最終結果準確的情況下,是會對指令進行重新排序的。就是說寫在前的代碼不一定先執(zhí)行,在后面的也不一定晚執(zhí)行。

      舉個例子:

      int a = 5; // 代碼1
      int b = 8; // 代碼2
      a = a + 4;	// 代碼3
      int c = a + b;	// 代碼4
      

      上面四行代碼的執(zhí)行順序有可能是
      在這里插入圖片描述
      JMM在是允許指令重排序的,在保證最后結果正確的情況下,處理器可以盡情的發(fā)揮,提高執(zhí)行效率。

      當多個線程執(zhí)行代碼的時候重排序的情況就更為突出了,各個CPU為了提高自己的效率,有可能會產(chǎn)生競爭情況,這樣就有可能導致最終執(zhí)行的正確性。

      所以為了保證在多個線程下最終執(zhí)行的正確性,將變量用volatile進行修飾,這樣就會達到禁止指令重排序的效果(其實也可以通過加鎖,還有一些其他已知規(guī)則來實現(xiàn)禁止指令重排序,但是我們這里只討論volatile的實現(xiàn)方式)。

      那么volatile是如何實現(xiàn)指令重排序的呢?

      答案是:內(nèi)存屏障

      內(nèi)存屏障是一組CPU指令,用于實現(xiàn)對內(nèi)存操作的順序限制。
      Java編譯器,會在生成指令系列時,在適當?shù)奈恢脮迦雰?nèi)存屏障來禁止處理器對指令的重新排序。

      volatile會在變量寫操作的前后加入兩個內(nèi)存屏障,來保證前面的寫指令和后面的讀指令是有序的。
      在這里插入圖片描述

      volatile在變量的讀操作后面插入兩個指令,禁止后面的讀指令和寫指令重排序。

      在這里插入圖片描述
      有序性,不僅只有volatile能保證,其他的實現(xiàn)方式也能保證,但是如果每一種實現(xiàn)方式都要了解那對于開發(fā)人員來說就比較困難了。

      所以從JDK5就出現(xiàn)了happen-before原則,也叫先行發(fā)生原則。
      先行發(fā)生原則總結起來就是:如果一個操作A的產(chǎn)生的影響能被另一個操作B觀察到,那么可以說,這個操作A先行發(fā)生與操作B。

      這里所說的影響包括內(nèi)存中的變量的修改,調(diào)用了方法,發(fā)送量消息等。

      volatile中的先行發(fā)生原則是,對一個volatile變量的寫操作,先行發(fā)生于后面任何地方對這個變量的讀操作。

      Volatile無法保證原子性

      原子性,是指一個操作過程要么都成功,要么都失敗,是一個獨立的完整的。

      就像上面說的,如果多個線程對一個變量進行累加,那么肯定得不到想要的結果,因為累加就不是一個原子操作。

      要保證累加最終結果正確,要么對累加變量加鎖,要么就用AotomicInteger這樣的變量。

      /**
       * 雙重檢查加鎖式單例
       */
      public class DoubleCheckLockSingleton implements Serializable{
      
          /**
           * 靜態(tài)變量,用來存放實例。
           */
          private volatile static DoubleCheckLockSingleton doubleCheckLockSingleton = null;
      
          /**
           * 私有化構造方法,禁止外部創(chuàng)建實例。
           */
          private DoubleCheckLockSingleton(){}
      
          /**
           * 雙重檢查加鎖的方式保證線程安全又能獲得到唯一實例
           * @return
           */
          public static DoubleCheckLockSingleton getInstance(){
              //第一次檢查實例是否已經(jīng)存在,不存在則進入代碼塊
              if(null == doubleCheckLockSingleton){
                  synchronized (DoubleCheckLockSingleton.class){
                      //第二次檢查
                      if(null==doubleCheckLockSingleton){
                          doubleCheckLockSingleton = new DoubleCheckLockSingleton();
                      }
                  }
              }
      
              return doubleCheckLockSingleton;
          }
      
      }
      

      為什么要進行雙重檢查呢?
      當?shù)谝粋€線程走到第一次檢查時發(fā)現(xiàn)對象為空,然后進入鎖,第二次就檢查時也為空,那么就去創(chuàng)建對象,但是這個時候又來了一個線程來到了第一次檢查,發(fā)現(xiàn)為空,但是這個時候因為鎖被占用,所以就只能阻塞等待,然后第一個線程創(chuàng)建對象成功了,由于對象是被volatile修飾的能夠立即反饋到其他線程上,所以在第一個線程釋放鎖之后,第二個線程進入了鎖,然后進行第二次檢查時,發(fā)現(xiàn)對象已經(jīng)被創(chuàng)建了,那么就不在創(chuàng)建對象了。從而保證的單例。

      還有就是如果創(chuàng)建對象,步驟:

      1. 分配內(nèi)存空間。
      2. 調(diào)用構造器,實例化。
      3. 返回內(nèi)存地址給引用。

      如果這三個指令順序被重排了,那么當多線程來獲取對象的時候就會造成對象雖然實例化了,但是沒有分配內(nèi)存空間,會有空指針的風險。
      所以加上了volatile的對象,也保證了在第二次檢查時不會被已經(jīng)在創(chuàng)建過程中的對象有被檢測為空的風險。

      總結一下

      volatile其實可以看作是輕量級的synchronized,雖然說volatile不能保證原子性,但是如果在多線程下的操作本身就是原子性操作(例如賦值操作),那么使用volatile會優(yōu)于synchronized

      volatile可以適用于,某個標識flag,一旦被修改了就需要被其他線程立即可見的情況。也可以修飾作為觸發(fā)器的變量,一旦變量被任何一個線程修改了,就去觸發(fā)執(zhí)行某個操作。

      volatile的變量寫操作happen-before,后面任何對此volatile變量的讀操作。

      posted @ 2020-11-06 08:26  紀莫  閱讀(1158)  評論(1)    收藏  舉報
      主站蜘蛛池模板: 高清中文字幕国产精品| 国产亚洲国产精品二区| 国产精品亚洲片夜色在线| 额尔古纳市| 加勒比久久综合网天天| 国产av无码专区亚洲aⅴ| 福利一区二区不卡国产| 久久亚洲精品成人综合网| 国产高清在线男人的天堂| 精品一区二区三区自拍图片区| 鲁一鲁一鲁一鲁一澡| 亚洲精品揄拍自拍首页一| 欧美videos粗暴| 天天看片视频免费观看| 亚洲精品国产综合麻豆久久99 | 亚洲国产精品高清久久久| 人妻聚色窝窝人体WWW一区| 国产99视频精品免费视频36| 毛片无码一区二区三区| 蜜臀av久久国产午夜| 在线精品自拍亚洲第一区| 中文字幕第一页国产精品| 亚洲天堂av 在线| 亚洲日韩性欧美中文字幕| 一本色道久久加勒比综合 | 亚洲日韩亚洲另类激情文学| 18禁超污无遮挡无码网址| 97人人模人人爽人人喊网| 久久精品国产色蜜蜜麻豆| 岛国中文字幕一区二区 | 最近中文国语字幕在线播放| 国产日韩精品中文字幕| 午夜精品区| 国产精品污双胞胎在线观看| 亚洲精品中文综合第一页| 黑人大荫道bbwbbb高潮潮喷| 国产成人精品av| 极品少妇的粉嫩小泬看片| 国产成人啪精品午夜网站| 性欧美大战久久久久久久| 精品一区二区无码免费|