成人怡红院-成人怡红院视频在线观看-成人影视大全-成人影院203nnxyz-美女毛片在线看-美女免费黄

站長資訊網
最全最豐富的資訊網站

Java基礎之volatile詳解

本篇文章給大家?guī)砹岁P于java的相關知識,其中主要整理了volatile的相關問題,包括了volatile保證可見性、volatile不保證原子性、volatile禁止指令重排等等內容,下面一起來看一下,希望對大家有幫助。

Java基礎之volatile詳解

推薦學習:《java視頻教程》

問:請談談你對volatile的理解?
答:volatile是Java虛擬機提供的輕量級的同步機制,它有3個特性
1)保證可見性
2)不保證原子性
3)禁止指令重排

剛學完java基礎,如果有人問你什么是volatile?它有什么作用的話,相信一定非常懵逼…
可能看了答案,也完全不明白,什么是同步機制?什么是可見性?什么是原子性?什么是指令重排?

1、volatile保證可見性

1.1、什么是JMM模型?

要想理解什么是可見性,首先要先理解JMM

JMM(Java內存模型,Java Memory Model)本身是一種抽象的概念,并不真實存在。它描述的是一組規(guī)則或規(guī)范,通過這組規(guī)范,定了程序中各個變量的訪問方法。JMM關于同步的規(guī)定:
1)線程解鎖前,必須把共享變量的值刷新回主內存;
2)線程加鎖前,必須讀取主內存的最新值到自己的工作內存;
3)加鎖解鎖是同一把鎖;

由于JVM運行程序的實體是線程,創(chuàng)建每個線程時,JMM會為其創(chuàng)建一個工作內存(有些地方稱為棧空間),工作內存是每個線程的私有數據區(qū)域。

Java內存模型規(guī)定所有變量都存儲在主內存,主內存是共享內存區(qū)域,所有線程都可以訪問。

但線程對變量的操作(讀取、賦值等)必須在工作內存中進行。因此首先要將變量從主內存拷貝到自己的工作內存,然后對變量進行操作,操作完成后再將變量寫會主內存中。

看了上面對JMM的介紹,可能還是優(yōu)點懵,接下來用一個賣票系統(tǒng)來進行舉例:

1)如下圖,此時賣票系統(tǒng)后端只剩下1張票,并已讀入主內存中:ticketNum=1。
2)此時網絡上有多個用戶都在搶票,那么此時就有多個線程同時都在進行買票服務,假設此時有3個線程都讀入了目前的票數:ticketNum=1,那么接著就會買票。
3)假設線程1先搶占到cpu的資源,先買好票,并在自己的工作內存中將ticketNum的值改為0:ticketNum=0,然后再寫回到主內存中。

此時,線程1的用戶已經買到票了,那么線程2,線程3此時應該不能再繼續(xù)買票了,因此需要系統(tǒng)通知線程2,線程3,ticketNum此時已經等于0了:ticketNum=0。如果有這樣的通知操作,你就可以理解為就具有可見性

Java基礎之volatile詳解

通過上面對JMM的介紹和舉例,可以簡單總結下。

JMM內存模型的可見性是指,多線程訪問主內存的某一個資源時,如果某一個線程在自己的工作內存中修改了該資源,并寫回主內存,那么JMM內存模型應該要通知其他線程來從新獲取最新的資源,來保證最新資源的可見性

1.2、volatile保證可見性的代碼驗證

在1.1中,已經基本理解了可見性的含義,接下來用代碼來驗證一下,volatile確實可以保證可見性。

1.2.1、無可見性代碼驗證

首先先驗證下,不使用volatile,是不是就是沒有可見性。

package com.koping.test;import java.util.concurrent.TimeUnit;class MyData{     int number = 0;      public void add10() {         this.number += 10;     }}public class VolatileVisibilityDemo {     public static void main(String[] args) {         MyData myData = new MyData();          // 啟動一個線程修改myData的number,將number的值加10         new Thread(                 () -> {                     System.out.println("線程" + Thread.currentThread().getName()+"t 正在執(zhí)行");                     try{                         TimeUnit.SECONDS.sleep(3);                     } catch (Exception e) {                         e.printStackTrace();                     }                     myData.add10();                     System.out.println("線程" + Thread.currentThread().getName()+"t 更新后,number的值為" + myData.number);                 }         ).start();          // 看一下主線程能否保持可見性         while (myData.number == 0) {             // 當上面的線程將number加10后,如果有可見性的話,那么就會跳出循環(huán);             // 如果沒有可見性的話,就會一直在循環(huán)里執(zhí)行         }          System.out.println("具有可見性!");     }}

運行結果如下圖,可以看到雖然線程0已經將number的值改為了10,但是主線程還是在循環(huán)中,因為此時number不具有可見性,系統(tǒng)不會主動通知。
Java基礎之volatile詳解

1.2.1、volatile保證可見性驗證

在上面代碼的第7行給變量number添加volatile后再次測試,如下圖,此時主線程成功退出了循環(huán),因為JMM主動通知了主線程更新number的值了,number已經不為0了。
Java基礎之volatile詳解

2、volatile不保證原子性

2.1 什么是原子性?

理解了上面說的可見性之后,再來理解下什么叫原子性

原子性是指不可分隔,完整性,即某個線程正在做某個業(yè)務時,中間不能被分割。要么同時成功,要么同時失敗。

還是有點抽象,接下來舉個例子。

如下圖,創(chuàng)建了一個測試原子性的類:TestPragma。在add方法中將n加1,通過查看編譯后的代碼可以看到,n++被拆分為3個指令進行執(zhí)行。

因此可能存在線程1正在執(zhí)行第1個指令,緊接著線程2也正在執(zhí)行第1個指令,這樣當線程1和線程2都執(zhí)行完3個指令之后,很容易理解,此時n的值只加了1,而實際是有2個線程加了2次,因此這種情況就是不保證原子性。
Java基礎之volatile詳解

2.2 不保證原子性的代碼驗證

在2.1中已經進行了舉例,可能存在2個線程執(zhí)行n++的操作,但是最終n的值卻只加了1的情況,接下來對這種情況再用代碼進行演示下。

首先給MyData類添加一個add方法

package com.koping.test;class MyData {     volatile int number = 0;      public void add() {         number++;     }}

然后創(chuàng)建測試原子性的類:TestPragmaDemo。測試下20個線程給number各加1000次之后,number的值是否是20000。

package com.koping.test;public class TestPragmaDemo {     public static void main(String[] args) {         MyData myData = new MyData();          // 啟動20個線程,每個線程將myData的number值加1000次,那么理論上number值最終是20000         for (int i=0; i<20; i++) {             new Thread(() -> {                 for (int j=0; j<1000; j++) {                     myData.add();                 }             }).start();         }          // 程序運行時,模型會有主線程和守護線程。如果超過2個,那就說明上面的20個線程還有沒執(zhí)行完的,就需要等待         while (Thread.activeCount()>2){             Thread.yield();         }          System.out.println("number值加了20000次,此時number的實際值是:" + myData.number);      }}

運行結果如下圖,最終number的值僅為18410。
可以看到即使加了volatile,依然不保證有原子性。
Java基礎之volatile詳解

2.3 volatile不保證原子性的解決方法

上面介紹并證明了volatile不保證原子性,那如果希望保證原子性,怎么辦呢?以下提供了2種方法

2.3.1 方法1:使用synchronized

方法1是在add方法上添加synchronized,這樣每次只有1個線程能執(zhí)行add方法。

結果如下圖,最終確實可以使number的值為20000,保證了原子性。

但是,實際業(yè)務邏輯方法中不可能只有只有number++這1行代碼,上面可能還有n行代碼邏輯。現(xiàn)在為了保證number的值是20000,就把整個方法都加鎖了(其實另外那n行代碼,完全可以由多線程同時執(zhí)行的)。所以就優(yōu)點殺雞用牛刀,高射炮打蚊子,小題大做了。

package com.koping.test;class MyData {     volatile int number = 0;      public synchronized void add() {       // 在n++上面可能還有n行代碼進行邏輯處理         number++;     }}

Java基礎之volatile詳解

2.3.2 方法1:使用JUC包下的AtomicInteger

給MyData新曾一個原子整型類型的變量num,初始值為0。

package com.koping.test;import java.util.concurrent.atomic.AtomicInteger;class MyData {     volatile int number = 0;      volatile AtomicInteger num = new AtomicInteger();      public void add() {         // 在n++上面可能還有n行代碼進行邏輯處理         number++;         num.getAndIncrement();     }}

讓num也同步加20000次。結果如下圖,可以看到,使用原子整型的num可以保證原子性,也就是number++的時候不會被搶斷。

package com.koping.test;public class TestPragmaDemo {     public static void main(String[] args) {         MyData myData = new MyData();          // 啟動20個線程,每個線程將myData的number值加1000次,那么理論上number值最終是20000         for (int i=0; i<20; i++) {             new Thread(() -> {                 for (int j=0; j<1000; j++) {                     myData.add();                 }             }).start();         }          // 程序運行時,模型會有主線程和守護線程。如果超過2個,那就說明上面的20個線程還有沒執(zhí)行完的,就需要等待         while (Thread.activeCount()>2){             Thread.yield();         }          System.out.println("number值加了20000次,此時number的實際值是:" + myData.number);         System.out.println("num值加了20000次,此時number的實際值是:" + myData.num);      }}

Java基礎之volatile詳解

3、volatile禁止指令重排

3.1 什么是指令重排?

在第2節(jié)中理解了什么是原子性,現(xiàn)在要理解下什么是指令重排?

計算機在執(zhí)行程序時,為了提高性能,編譯器和處理器常常會對指令進行重排
源代碼–>編譯器優(yōu)化重排–>指令并行重排–>內存系統(tǒng)重排–>最終執(zhí)行指令

處理器在進行重排時,必須要考慮指令之間的數據依賴性

單線程環(huán)境中,可以確保最終執(zhí)行結果和代碼順序執(zhí)行的結果一致。

但是多線程環(huán)境中,線程交替執(zhí)行,由于編譯器優(yōu)化重排的存在,兩個線程使用的變量能否保持一致性是無法確定的,結果無法預測

看了上面的文字性表達,然后看一個很簡單的例子。
比如下面的mySort方法,在系統(tǒng)指令重排后,可能存在以下3種語句的執(zhí)行情況:
1)1234
2)2134
3)1324
以上這3種重排結果,對最后程序的結果都不會有影響,也考慮了指令之間的數據依賴性。

public void mySort() {     int x = 1;  // 語句1     int y = 2;  // 語句2     x = x + 3;  // 語句3     y = x * x;  // 語句4}

3.2 單線程單例模式

看完指令重排的簡單介紹后,然后來看下單例模式的代碼。

package com.koping.test;public class SingletonDemo {     private static SingletonDemo instance = null;      private SingletonDemo() {         System.out.println(Thread.currentThread().getName() + "t 執(zhí)行構造方法SingletonDemo()");     }      public static SingletonDemo getInstance() {         if (instance == null) {             instance = new SingletonDemo();         }         return instance;     }      public static void main(String[] args) {         // 單線程測試         System.out.println("單線程的情況測試開始");         System.out.println(SingletonDemo.getInstance() == SingletonDemo.getInstance());         System.out.println(SingletonDemo.getInstance() == SingletonDemo.getInstance());         System.out.println("單線程的情況測試結束n");     }}

首先是在單線程情況下進行測試,結果如下圖。可以看到,構造方法只執(zhí)行了一次,是沒有問題的。
Java基礎之volatile詳解

3.3 多線程單例模式

接下來在多線程情況下進行測試,代碼如下。

package com.koping.test;public class SingletonDemo {     private static SingletonDemo instance = null;      private SingletonDemo() {         System.out.println(Thread.currentThread().getName() + "t 執(zhí)行構造方法SingletonDemo()");     }      public static SingletonDemo getInstance() {         if (instance == null) {             instance = new SingletonDemo();         }          // DCL(Double Check Lock雙端檢索機制)//        if (instance == null) {//            synchronized (SingletonDemo.class) {//                if (instance == null) {//                    instance = new SingletonDemo();//                }//            }//        }         return instance;     }      public static void main(String[] args) {         // 單線程測試//        System.out.println("單線程的情況測試開始");//        System.out.println(SingletonDemo.getInstance() == SingletonDemo.getInstance());//        System.out.println(SingletonDemo.getInstance() == SingletonDemo.getInstance());//        System.out.println("單線程的情況測試結束n");          // 多線程測試         System.out.println("多線程的情況測試開始");         for (int i=1; i<=10; i++) {             new Thread(() -> {                 SingletonDemo.getInstance();             }, String.valueOf(i)).start();         }     }}

在多線程情況下的運行結果如下圖。可以看到,多線程情況下,出現(xiàn)了構造方法執(zhí)行了2次的情況。
Java基礎之volatile詳解

3.4 多線程單例模式改進:DCL

在3.3中的多線程單里模式下,構造方法執(zhí)行了兩次,因此需要進行改進,這里使用雙端檢鎖機制:Double Check Lock, DCL。即加鎖之前和之后都進行檢查。

package com.koping.test;public class SingletonDemo {     private static SingletonDemo instance = null;      private SingletonDemo() {         System.out.println(Thread.currentThread().getName() + "t 執(zhí)行構造方法SingletonDemo()");     }      public static SingletonDemo getInstance() {//        if (instance == null) {//            instance = new SingletonDemo();//        }          // DCL(Double Check Lock雙端檢鎖機制)         if (instance == null) {  // a行             synchronized (SingletonDemo.class) {                 if (instance == null) {  // b行                     instance = new SingletonDemo();  // c行                 }             }         }         return instance;     }      public static void main(String[] args) {         // 單線程測試//        System.out.println("單線程的情況測試開始");//        System.out.println(SingletonDemo.getInstance() == SingletonDemo.getInstance());//        System.out.println(SingletonDemo.getInstance() == SingletonDemo.getInstance());//        System.out.println("單線程的情況測試結束n");          // 多線程測試         System.out.println("多線程的情況測試開始");         for (int i=1; i<=10; i++) {             new Thread(() -> {                 SingletonDemo.getInstance();             }, String.valueOf(i)).start();         }     }}

在多次運行后,可以看到,在多線程情況下,此時構造方法也只執(zhí)行1次了。
Java基礎之volatile詳解

3.5 多線程單例模式改進,DCL版存在的問題

需要注意的是3.4中的DCL版的單例模式依然不是100%準確的!!!

是不是不太明白為什么3.4DCL版單例模式不是100%準確的原因
是不是不太明白在3.1講完指令重排的簡單理解后,為什么突然要講多線程的單例模式

因為3.4DCL版單例模式可能會由于指令重排而導致問題,雖然該問題出現(xiàn)的可能性可能是千萬分之一,但是該代碼依然不是100%準確的。如果要保證100%準確,那么需要添加volatile關鍵字,添加volatile可以禁止指令重排

接下來分析下,為什么3.4DCL版單例模式不是100%準確?

查看instance = new SingletonDemo();編譯后的指令,可以分為以下3步:
1)分配對象內存空間:memory = allocate();
2)初始化對象:instance(memory);
3)設置instance指向分配的內存地址:instance = memory;

由于步驟2和步驟3不存在數據依賴關系,因此可能出現(xiàn)執(zhí)行132步驟的情況。
比如線程1執(zhí)行了步驟13,還沒有執(zhí)行步驟2,此時instance!=null,但是對象還沒有初始化完成;
如果此時線程2搶占到cpu,然后發(fā)現(xiàn)instance!=null,然后直接返回使用,就會發(fā)現(xiàn)instance為空,就會出現(xiàn)異常

這就是指令重排可能導致的問題,因此要想保證程序100%正確就需要加volatile禁止指令重排。

3.6 volatile保證禁止指令重排的原理

在3.1中簡單介紹了下執(zhí)行重排的含義,然后通過3.2-3.5,借助單例模式來舉例說明多線程情況下,為什么要使用volatile的原因,因為可能存在指令重排導致程序異常

接下來就介紹下volatile能保證禁止指令重排的原理。

首先要了解一個概念:內存屏障(Memory Barrier),又稱為內存柵欄。它是一個CPU指令,有2個作用:
1)保證特定操作的執(zhí)行順序
2)保證某些變量的內存可見性

由于編譯器和處理器都能執(zhí)行指令重排。如果在指令之間插入一條Memory Barrier則會告訴編譯器和CPU,不管什么指令都不能和這條Memory Barrier指令重排序,也就是說,通過插入內存屏障,禁止在內存屏障前后的指令執(zhí)行重排需優(yōu)化

內存屏障的另一個作用是強制刷出各種CPU的緩存數據,因此任何CPU上的線程都能讀取到這些數據的最新版本

Java基礎之volatile詳解

推薦學習:《java視頻教程》

贊(0)
分享到: 更多 (0)
?
網站地圖   滬ICP備18035694號-2    滬公網安備31011702889846號
98人妻人人揉人人躁88Av| 一本精品中文字幕在线| 亚洲ΑV久久久噜噜噜噜噜| 亚洲成A人片在线观看无码下载| 亚洲乱码av无码一区二区三区 | 亚洲国产成人无码精品| 亚洲最大AV网站在线观看| 2012电影在线观看神马影院| Y11111少妇无码电影| 粉嫩人妻一区二区三区| 国产精品一卡二卡三卡四卡| 精品久久8X国产免费观看| 美女又大又黄WWW免费网站| 人妻精品久久久久中文字幕69| 少妇粉嫩小泬喷水视频在线观看| 性色A∨人人爽网站| 亚洲线精品一区二区三区影音先锋| 中文字幕日韩人妻在线乱码| 被群CAO的合不拢腿H纯肉视频| 国产精品国产三级国产AV剧情| 精品熟女AV少妇免费久久自慰| 内射人妻无码色AV天堂| 色综合久久精品亚洲国产消防| 亚洲AV成人无码一区在线观看| 一本大道无码日韩精品影视_| А√在线天堂中文| 国产美女久久精品香蕉69| 久久久久亚洲AV无码专| 人善之交Z0OZO0D0G人善| 五十熟妇日本熟妇久久| 幼儿HIPHOP仙踪林的功能| 成人无码免费一区二区三区| 狠狠人妻久久久久久综合蜜桃| 美女自拍高潮流白浆| 少妇的BBW性大片| 亚洲日韩在线成人AV电影网站| 99久久国产综合精品五月天喷水| 国产精品久久久久久福利| 久久夜色撩人精品国产小说| 日本高清视频网站WWW| 亚洲VA久久久噜噜噜熟女8 | 亚洲国产AV一区二区三区丶| 99热国产这里只有精品9| 国产精品人人做人人爽| 美女下部裸体张开腿视频| 为老公升职我主动奉献的句子说说| 国模生殖欣赏337METCN| 亚洲午夜精品久久久久久APP| 用力挺进她的花苞| 亚洲精品AⅤ在线观看| 亚洲AV自慰白浆喷水网站少妇| 天天拍夜夜添久久精品| 琪琪午夜伦伦电影理论片| 欧美XXXX色视频在线观看| 欧美成人精品高清视频| 久久久水蜜桃国产成人网站| 久久九九久精品国产| 精品一区二区三区在线成人| 久久理论片午夜琪琪电影网| 国产乱人伦无无码视频试看| 国产美女精品一区二区三区| 国产网红无码精品视频| 好男人WWW在线影院官网| 精品国精品无码自拍自在线| 教室停电了校草挺进我体内| 久久99精品久久久久婷婷暖| 激情综合婷婷丁香五月情| 久久精晶国产99久久6| 好爽好紧好大的免费视频国产| 久久99精品久久久久久水蜜桃| 久久久久亚洲AV成人人电影| 女人18片毛片免费| 人妻少妇乱子伦无码专区| 天天AV天天爽无码中文| 熟妇人交VIDEOS复古| 亚洲AV永久无码精品成人| 永久免费AV无码网站在线| 一本之道无码一区二区| YYYY11111少妇影院| 做AJ的姿势教程大全图片高清版| 国产成年女人特黄特色毛片免| 国产成人AV大片在线播放| 狠狠色婷婷久久一区二区| 久久精品人妻系列无码专区| 欧美日韩成人在线观看| 无码中文字幕AⅤ精品影院| 有码中文AV无码中文AV| 成午夜福利人试看120秒 | 人人妻人人澡人人爽人人蜜臀 | 久久精品人妻一区二区三区| 欧美成人片在线观看网站| 玩弄白嫩少妇XXXXX性| 中文国产成人精品久久APP| 国产精品人人做人人爽人人添 | 99国产精品国产精品九九| 被吊起来张开腿供人玩弄| 狠狠久久精品中文字幕无码| 人妻在厨房被侮辱高清版| 亚洲深深色噜噜狠狠网站| 夫妇联欢会回不去的夜晚| 精品BBBBB性ⅩXXXX少妇| 日本又黄又爽又色又刺激的视频| 亚洲人ⅤSAⅤ国产精品| 国产成人无码精品一区在线观看| 免费午夜无码18禁无码影视 | 一本大道色卡1卡2卡3| 宝宝下面都湿透了还嘴硬疼 | 玩弄秘书的奶又大又软| CHINESEMATURE性老| 久久久水蜜桃国产成人网站| 亚洲.国产.欧美一区二区三区| Chinese高潮老女人| 久久精品国产一区二区三区不卡| 日本强伦姧人妻69影院| 中文字幕亚洲人妻| 狠狠躁狠狠躁东京热无码专区| 欧美激情精品久久久久久黑人| 野花日本免费完整版高清版| 黑人又大又粗又硬XXXXX免费| 我和几个亲戚都做了爱| 99精品视频在线观看婷婷| 黑人玩弄人妻中文在线| 性少妇中国内射XXXX狠干| 嗯…啊 摸 湿 奶头免费视频| 人妻AⅤ无码一区二区三区| 亚洲日本一线产区和二线| 国产丰满大乳无码免费播放| 日韩无码视频专区| 岛国精品一区免费视频在线观看| 欧美熟妇成人大片性爽| 亚洲综合精品第一页| 国内女人喷潮完整视频| 色婷婷AV一区二区三区在线观看| 又色又爽又黄的视频软件APP | 久久亚洲中文字幕精品一区| 夜夜揉揉日日人人| 免费真人视频APP| 八戒电影电影网电影网| 日韩精品一区二区三区在线观看L| 成人A片产无码免费视频在线观看 成人A毛片免费全部播放 | 部长的夫人的味道中字| 色偷偷亚洲女人的天堂| 国产高清在线A视频大全| 亚洲AV乱码久久精品密桃| 国产精品天干天干在线观看| 亚洲AV无码乱码忘忧草亚洲人| 国产 中文 制服丝袜 另类| 人禽杂交18禁网站免费| 99久久亚洲综合精品成人网| 免费无遮挡色视频网站| 一夲道中文字幕AV高清片| 欧美日韩精品视频一区二区三区| 亚洲AV香蕉一区区二区三区 | 午夜成人亚洲理论片在线观看| 黑人精品XXX一区一二区| 玉蒲团之玉女心经| 日本大一大二大三在一起读吗| 国产AV导航大全精品| 污污污污污污WWW网站免费| 精品第一国产综合精品蜜芽| 亚洲无日韩码精品| 六月丁香婷婷色狠狠久久| 成长人短视频B站| 小SAO货边洗澡边CAO你| 国偷自产一区二区免费视频| 中文亚洲AV片在线观看无码| 少妇人妻偷人精品视频| 国产精品无码午夜福利| 在线A级毛片无码免费真人| 欧美日韩中文国产一区发布| 国产又黄又大又粗的视频| 337P日本欧洲亚洲大胆| 小洞饿了想吃大香肠| 欧美人交A欧美精品AV一区| 薄荷奶糖(1V2)笔趣| 亚洲AV高清在线一区二区三区| 欧美成人性生免费看的| 禁欲少校大哥的囚禁| OM老熟女HDXⅩXXX69| 亚洲色欲色欲欲WWW在线| 天天爽天天狠久久久综合麻豆 | 久热中文字幕无码视频| 宝宝下面都湿透了还嘴硬疼| 五月香丁激情欧美啪啪| 欧美一级 片内射黑人| 精品无码久久久久国产动漫3D| 风流少妇又紧又爽又丰满| 亚洲日韩欧洲无码A∨夜夜 | 国产精品电影久久久久电影网| 中文字AV字幕在线观看| 亚洲AV成人一区二区三区观看在| 全力以赴的行动派第二季| 麻花豆传媒剧国产MV的特点| 国产精品99久久久久久宅男小说| AV无码小缝喷白浆在线观看| 亚洲中文字幕无码中文字在线| 性无码免费一区二区三区在线| 色欲老女人人妻综合网 | XOXOXO性ⅩYY欧美片| 亚洲色大18成人网站WWW在线|