91超碰碰碰碰久久久久久综合_超碰av人澡人澡人澡人澡人掠_国产黄大片在线观看画质优化_txt小说免费全本

溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點擊 登錄注冊 即表示同意《億速云用戶服務條款》

什么是volatile

發布時間:2021-10-13 10:35:20 來源:億速云 閱讀:103 作者:iii 欄目:編程語言

本篇內容主要講解“什么是volatile”,感興趣的朋友不妨來看看。本文介紹的方法操作簡單快捷,實用性強。下面就讓小編來帶大家學習“什么是volatile”吧!

volatile的3個特性:

  • 保證了各個線程之間的可見性

  • 不能保證原子性

  • 防止重排序

可見性:

首先,每個線程都有自己的工作內存,除此之外還有一個cpu的主存,工作內存是主存的副本。線程工作的時候,不能直接操作主內存中的值,而是要將主存的值拷貝到自己的工作內存中;在修改變量是,會先在工作內存中修改,隨后刷新到主存中。

注意: 什么時候線程需要將主存中的值拷貝到工作內存

  • 線程中釋放鎖的時

  • 線程切換時

  • CPU有空閑時間時(比如線程休眠時)

什么是volatile

假設有一個共享變量flag為false,線程a修改為true后,自己的工作內存修改了,也刷新到了主存。這時候線程b對flag進行對應操作時,是不知道a修改了的,也稱a對b不可見。所以我們需要一種機制,在主存的值修改后,及時地通知所有線程,保證它們都可以看到這個變化。

public class ReadWriteDemo {
	
    //對于flag并沒有加volatile
    public boolean flag = false;
    public void change() {
        flag = true;
        System.out.println("flag has changed:" + flag);
    }

    public static void main(String[] args) {

        ReadWriteDemo readWriteDemo = new ReadWriteDemo();
        //創建一個線程,用來修改flag,如上面描述的a線程
        new Thread(new Runnable() {
            @Override
            public void run() {
                try {
                    Thread.sleep(3000);
                    readWriteDemo.change();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }).start();

        //主線程,如上面描述的b線程
        while(!readWriteDemo.flag) {
        }
        System.out.println("flag:" + readWriteDemo.flag);
    }

}

按照分析,沒有加volatile的話,主線程(b線程)是看不到子線程(a線程)修改了flag的值。也就是說,在主線程看來,在沒有特殊情況下,flag 永遠為false, while(!readWriteDemo.flag) {}的判斷條件為true,系統不會執行到System.out.println("flag:" + readWriteDemo.flag);

什么是volatile

為了避免偶然性,我讓程序跑了6分鐘。可以看到,子線程確實修改了flag的值,主線程也和我們預期一樣,看不到flag的變化,一直在死循環。如果給flag變量加一個volatile呢,預期結果是,子線程修改變量對主線程來說是可見的,主線程會退出循環。

什么是volatile

可以看到,都不到一分鐘,在子線程修改flag的值后,主線程隨即就退出循環,說明立刻感知到了flag變量的變化。

有趣的是什么呢:如果ab兩個線程間隔時間不長,當b線程也延遲10s讀(不是上面的立刻讀),你會發現兩個線程之間的修改也是可見的,為什么呢,stakc overflow上有解答,執行該線程的cpu有空閑時,會去主存讀取以下共享變量來更新工作內存中的值。更有趣的是,在寫這篇文章的時候,cpu及內存是這樣的,反而能正常執行,但是能出現問題就能說明volatile的作用。

什么是volatile

如何保證可見性:

首先要先講一下java內存模型,java的的內存模型規定了工作內存與主存之間交互的協議,定義了8中原子操作:

  1. lock:將主內存的變量鎖定,為一個線程所獨占。

  2. unlock:將lock加的鎖定解除,此時其他線程可以有機會訪問此變量。

  3. read:將主內存中的變量值讀到工作線程中。

  4. load:將read讀取到的值保存到工作內存中的變量副本中。

  5. use:將值傳遞給線程的代碼執行引擎。

  6. assign:將執行引擎處理返回的值重新賦值給變量副本。

  7. store:將變量副本的值存儲到主內存中。

  8. write:將store存儲的值寫入到主內存的共享變量中。

我上網查了下資料,也看了不同的博客,有講到volatile其實在底層就是加了一個lock的前綴指令。lock前綴的指令要干什么上面也有寫。如果對帶有volatile的變量進行寫操作會怎么呢。JVM會像處理器發送一條lock前綴的指令,a線程就鎖定主存內的變量,修改后再刷新到主存。b線程同樣會鎖定主存內的變量,但是會發現主存內的變量和工作內存的值不一樣,就會從主存中讀取最新的值。從而保證了每個線程都能對變量的改變可見。

原子性:

在編程世界里面,原子性是指不能分割的操作,一個操作要么全部執行,要么全部不執行,是執行的最小單元。

public class TestAutomic {
    volatile int num = 0;
    void add() {
        num++;
    }

    public static void main(String[] args) throws InterruptedException {
        TestAutomic testAutomic = new TestAutomic();
        for (int i = 0; i < 1000; i++) {
            new Thread(new Runnable() {
                @Override
                public void run() {
                    try {
                        Thread.sleep(10);
                        testAutomic.add();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }).start();
        }
        //等待12秒,讓子線程全部執行完
        Thread.sleep(12000);
        System.out.println(testAutomic.num);
    }

}

**預期現象:**都說不能保證原子性了,所以,應該結果是不等于1000

什么是volatile

不同電腦執行的結果不一樣,我的是886,可能你們的不是,但是都說明了volatile都無法保證操作的原子性。

為什么不能保證原子性:

這要從num++操作開始講起,num++操作可以分為三步:

  • 讀取i的值,裝載進工作內存

  • 對i加1操作

  • 將i的值寫回工作內存,刷新到主存中

我們知道線程的執行具有隨機性,假設a線程和b線程中的工作內存中都是num=0,a線程先搶了cpu的執行權,在工作內存進行了加1操作,還沒刷新到主存中;b線程這時候拿到了cpu的執行權,也加1;接著a線程刷新到主存num=1,而b線程刷新到主存,同樣是num=1,但是兩次操作后num應該等于2。

解決方案:

  • 使用synchronized關鍵字

  • 使用原子類

重排序:

對于我們寫的程序,cpu會根據如何讓程序更高效來對指令經行重排序,什么意思呢

a = 2;
b = new B();
c = 3;
d = new D();

經過優化后,可能真實的指令順序是:

a = 2;
c = 3;
b = new B();
d = new D();

并不是所有的指令都會重排序,重排序與否全是看能不能使得指令更高效,還有下面一種情況。

a = 2;
b = a;

這兩行代碼無論什么情況下都不會重排序,因為第二條指令是依賴第一條指令的,重排序是建立在排序后最終結果仍然保持不變的基礎上。下面將給出volatile防止重排序的例子:

public class TestReorder {
    private static int a = 0, b = 0, x = 0, y = 0;

    public static void main(String[] args) throws InterruptedException {
        while (true) {
            a = 0; b = 0; x = 0; y = 0;
            //a線程
            new Thread(new Runnable() {
                @Override
                public void run() {
                    try {
                        Thread.sleep(10);
                        a = 1;
                        x = b;
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }

                }
            }).start();

            //b線程
            new Thread(new Runnable() {
                @Override
                public void run() {
                    try {
                        Thread.sleep(10);
                        b = 1;
                        y = a;
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }

                }
            }).start();

            //主線程睡100ms,以保證子線程全部執行完
            Thread.sleep(100);
            System.out.println("a=" + a + ";b=" + b + ";x=" + x + ";y=" + y);

        }
    }

}

還記得上面說過兩個線程如果沉睡時間差不多,它們之間是可見

預期結果:

  • 如果先執行a線程(a = 1, x = b = 0),再執行b線程(b = 1, y = a = 1),最終結果a = 1; b = 1; x = 0; y = 1

  • 如果先執行b線程(b = 1, y = a = 0),再執行a線程(a = 1, x = b = 1),最終結果a = 1; b = 1; x = 1; y = 0

  • 如果執行a線程過程(a = 1),接著執行了b線程(b = 1,y = a = 1)【為什么y = a一定等于1,因為它們兩個之間的改變是可見的】,最后執行了a線程(x = b = 1),最終結果a = 1;b = 1; x = 1; y = 1

什么是volatile

可以發現除了上面預期的三種情況,還出現了一種a = 1; b = 1; x = 0; y = 0的情況,相信大家也知道了,這種情況就是因為重排序造成的。要么是a線程重排序先執行x = b;再執行a = 1;,要么是b線程重排序先執行了y = a;再執行了b = 1;;要么是兩個線程都重排序了。

如果private volatile static int a = 0, b = 0, x = 0, y = 0;加了volatile關鍵字會怎么樣呢?

什么是volatile

為了保證正確性,又持續跑了5分鐘,可以發現,確實不會再出現x=0;y=0的情況。

如何防止重排序

先來講講4個內存屏障的作用

內存屏障作用
StoreStore屏障禁止上面的普通寫和下面的的volatile寫重排序
StoreLoad屏障禁止上面的volatile寫和下面volatile讀/寫重排序
LoadLoad屏障禁止下面的普通讀和上面的volatile讀重排序
LoadStore屏障禁止下面的普通寫和上面的volatile讀重排序

可能看作用比較抽象,直接舉例子叭

  • 對于S1; StoreStore; S2,在S2及后續寫入操作之前,保證S1的寫入操作對其它線程可見。

  • 對于S; StoreLoad; L,在L及后續讀/寫操作之前,保證S的寫入對其它線程可見。

  • 對于L1; LoadLoad; L2,在L2及后續讀操作之前,保證L1讀取數據完畢。

  • 對于L; LoadStore; S,在S及后續操作之前,保證L讀取數據完畢。

那么volatile是如何保證有序性的呢?

  • 在每個volatile寫操作前插入StoreStore屏障,每個寫操作后面加一個StoreLoad屏障。

  • 在每個volatile讀操作前插入LoadLoad屏障,在讀操作后插入LoadStore屏障。

舉例,有個對volatile變量的寫S,有個對volatile變量的讀L,會怎么樣呢。

  • 對于寫:S1; StoreStore; S ;StoreLoad L這樣能夠把S(對volatile變量保護在中間)防止重排序。

  • 對于讀一樣的道理:L1; LoadLoad; L ; LoadStore S,一樣把volatile變量保護的好好的。

到此,相信大家對“什么是volatile”有了更深的了解,不妨來實際操作一番吧!這里是億速云網站,更多相關內容可以進入相關頻道進行查詢,關注我們,繼續學習!

向AI問一下細節

免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。

AI

兴山县| 永和县| 河源市| 沈阳市| 泰安市| 武汉市| 乌拉特中旗| 曲阜市| 桃源县| 浑源县| 防城港市| 岢岚县| 济阳县| 巴彦淖尔市| 仲巴县| 赤峰市| 邵武市| 长葛市| 瑞丽市| 万全县| 克东县| 利辛县| 奉贤区| 怀远县| 南开区| 隆化县| 延川县| 马龙县| 南丹县| 竹溪县| 牡丹江市| 临汾市| 邛崃市| 南城县| 静海县| 罗甸县| 崇义县| 宾阳县| 和硕县| 常山县| 炉霍县|