volatile引發(fā)的一個(gè)有趣的測(cè)試

轉(zhuǎn)載請(qǐng)以鏈接形式標(biāo)明出處:
本文出自:103style的博客

本文是 看到 這篇文章中 “volatile 的意義痹束?” 那一小節(jié)提供的一個(gè)例子引發(fā)的測(cè)試软驰。

volatile 的意義觅廓?

  • 防止CPU指令重排序

volatile有兩條關(guān)鍵的語義:

  • 保證被volatile修飾的變量對(duì)所有線程都是可見的
  • 禁止進(jìn)行指令重排序

volatile變量具有下列特性:

  • 可見性:總是能看到(任意線程)對(duì)這個(gè)volatile變量最后的寫入。
  • 原子性:對(duì)任意單個(gè)volatile變量的讀/寫具有原子性驼侠,但類似于volatile++這種復(fù)合操作不具有原子性零聚。

下面的例子是用來證明下面這個(gè)觀點(diǎn)的后半句是錯(cuò)誤的。

由于volatile修飾的變量在各個(gè)線程里都是一致的拉队,所以基于volatile變量的運(yùn)算在多線程并發(fā)的情況下是安全的弊知。


原測(cè)試示例

例子是這樣的:

public class Test {
    private volatile int start = 0;
    public static void main(String[] args) {
        new Test().test();
    }
    private void test() {
        Runnable runnable = new Runnable() {
            @Override
            public void run() {
                add();
            }
        };
        for (int i = 0; i < 10; i++) {
            Thread thread = new Thread(runnable);
            thread.start();
        }
        System.out.println("start = " + start);
    }
    void add() {
        for (int i = 0; i < 10; i++) {
            start++;
        }
    }
}

大家可以看看 最后運(yùn)行的結(jié)果輸出的 start 值是多少 ?


給 add 方法加上 synchronized 之后

add() 方法加上 synchronized 之后輸出的 start 值又是多少呢 ?

public class Test {
    private volatile int start = 0;
    public static void main(String[] args) {
        new Test().test();
    }
    private void test() {
        Runnable runnable = new Runnable() {
            @Override
            public void run() {
                add();
            }
        };
        for (int i = 0; i < 10; i++) {
            Thread thread = new Thread(runnable);
            thread.start();
        }
        System.out.println("start = " + start);
    }
    synchronized void add() {
        for (int i = 0; i < 10; i++) {
            start++;
        }
    }
}

兩個(gè)測(cè)試的結(jié)果

兩個(gè)結(jié)果都是 100粱快? 還是只有第二個(gè)是100 ?

其實(shí)兩個(gè)結(jié)果都不是100秩彤,這是因?yàn)?main方法對(duì)應(yīng)的線程 不會(huì)等待 新創(chuàng)建的線程執(zhí)行完。

我們可以加上時(shí)間輸出看看試試:

public class Test {
    private volatile int start = 0;
    public static void main(String[] args) {
        new Test().test();
    }
    private void test() {
        Runnable runnable = new Runnable() {
            @Override
            public void run() {
                add();
                System.out.println("new thread: " + System.currentTimeMillis());
            }
        };
        for (int i = 0; i < 10; i++) {
            Thread thread = new Thread(runnable);
            thread.start();
        }
        System.out.println("start = " + start);
        System.out.println("main: " + System.currentTimeMillis());
    }
    void add() {
        for (int i = 0; i < 10; i++) {
            start++;
        }
    }
}

控制臺(tái)打印的結(jié)果:

new thread: 1583915204005
new thread: 1583915204005
new thread: 1583915204007
new thread: 1583915204007
new thread: 1583915204007
new thread: 1583915204007
start = 60
main: 1583915204007
new thread: 1583915204008
new thread: 1583915204008
new thread: 1583915204008
new thread: 1583915204008

可以看到 main方法對(duì)應(yīng)的線程 先執(zhí)行完了事哭。

然后為了解決 main方法對(duì)應(yīng)的線程 先執(zhí)行完漫雷, 我們加上 Thread.sleep(1000); 看看:

public class Test {
    private volatile int start = 0;
    public static void main(String[] args) {
        new Test().test();
    }
    private void test() {
        Runnable runnable = new Runnable() {
            @Override
            public void run() {
                add();
                System.out.println("new thread: " + System.currentTimeMillis());
            }
        };
        for (int i = 0; i < 10; i++) {
            Thread thread = new Thread(runnable);
            thread.start();
        }
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("start = " + start);
        System.out.println("main: " + System.currentTimeMillis());
    }
    void add() {
        for (int i = 0; i < 10; i++) {
            start++;
        }
    }
}

查看控制臺(tái)輸出:

new thread: 1583915390819
new thread: 1583915390821
new thread: 1583915390822
new thread: 1583915390823
new thread: 1583915390823
new thread: 1583915390823
new thread: 1583915390823
new thread: 1583915390823
new thread: 1583915390823
new thread: 1583915390823
start = 100
main: 1583915391822

去掉修飾 start 的 volatile 修飾符

然后我們?cè)囋嚢?volatile 修飾符 去掉試試,運(yùn)行的結(jié)果輸出的 start 值是多少 ?

我們修改代碼如下:

public class Test {
    private int start = 0;
    public static void main(String[] args) {
        List<Integer> res = new ArrayList<>();
        for (int i = 0; i < 20; i++) {
            res.add(new Test().test());
        }
        System.out.println(res);
    }
    private int test() {
        Runnable runnable = () -> add();
        for (int i = 0; i < 10; i++) {
            Thread thread = new Thread(runnable);
            thread.start();
        }
        try {
            Thread.sleep(100);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        return start;
    }
    void add() {
        for (int i = 0; i < 10; i++) {
            start++;
        }
    }
}

查看控制臺(tái)輸出:

[100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100]

就不賣關(guān)子了鳍咱,這里結(jié)果沒問題的原因主要是因?yàn)?創(chuàng)建線程的耗時(shí) 比 add 方法執(zhí)行的耗時(shí)還長降盹, 所以就相當(dāng)與單線程執(zhí)行了,我們可以來驗(yàn)證下谤辜。

public class Test {
    private int start = 0;
    public static void main(String[] args) {
        new Test().test();
    }
    private int test() {
        for (int i = 0; i < 10; i++) {
            long t = System.nanoTime();
            Thread thread = new Thread(new TestRunnable(i));
            System.out.println(i + "_new_thred: " + (System.nanoTime() - t));
            thread.start();
        }
        try {
            Thread.sleep(100);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        return start;
    }
    void add() {
        for (int i = 0; i < 10; i++) {
            start++;
        }
    }
    private class TestRunnable implements Runnable {
        int id;
        public TestRunnable(int i) {
            id = i;
        }
        @Override
        public void run() {
            long t = System.nanoTime();
            add();
            System.out.println(id + "_add: " + (System.nanoTime() - t));
        }
    }
}

查看控制臺(tái)輸出:

0_new_thred: 1232700
1_new_thred: 31800
2_new_thred: 18000
3_new_thred: 24500
0_add: 62100
4_new_thred: 19700
5_new_thred: 76800
3_add: 19200
6_new_thred: 22300
7_new_thred: 24500
8_new_thred: 32000
9_new_thred: 26100
4_add: 23100
8_add: 20000
7_add: 18400
1_add: 20900
2_add: 19600
5_add: 40300
9_add: 22100
6_add: 23600

當(dāng)我們修改 add 方法的次數(shù)為 10W 次之后:

public class Test {
    private int start = 0;
    public static void main(String[] args) {
        List<Integer> res = new ArrayList<>();
        for (int i = 0; i < 10; i++) {
            res.add(new Test().test());
        }
        System.out.println(res);
    }
    private int test() {
        Runnable runnable = () -> add();
        ExecutorService executorService = Executors.newFixedThreadPool(100);
        for (int i = 0; i < 100; i++) {
            executorService.execute(runnable);
        }
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        executorService.shutdown();
        return start;
    }
    void add() {
        for (int i = 0; i < 100000; i++) {
            start++;
        }
    }
}

查看控制臺(tái)輸出:

[9337838, 9957329, 10000000, 10000000, 10000000, 9925170, 10000000, 9922369, 10000000, 10000000]

修改上面的測(cè)試代碼蓄坏,給 start 添加 volatile 修飾符:

public class Test {
    private volatile int start = 0;
    public static void main(String[] args) {
        List<Integer> res = new ArrayList<>();
        for (int i = 0; i < 10; i++) {
            res.add(new Test().test());
        }
        System.out.println(res);
    }
    private int test() {
        Runnable runnable = () -> add();
        ExecutorService executorService = Executors.newFixedThreadPool(100);
        for (int i = 0; i < 100; i++) {
            executorService.execute(runnable);
        }
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        executorService.shutdown();
        return start;
    }
    void add() {
        for (int i = 0; i < 100000; i++) {
            start++;
        }
    }
}

查看控制臺(tái)輸出:

[2292403, 2449807, 2146843, 1899033, 2120498, 4689152, 2264998, 2181451, 2266435, 2443323]

可以發(fā)現(xiàn)結(jié)果也是不對(duì)的价捧。


執(zhí)行結(jié)果正確的代碼

要正確輸出結(jié)果我們可以修改代碼如下:

public class Test {
    private int start = 0;
    public static void main(String[] args) {
        List<Integer> res = new ArrayList<>();
        for (int i = 0; i < 10; i++) {
            res.add(new Test().test());
        }
        System.out.println(res);
    }

    private int test() {
        Runnable runnable = () -> add();
        ExecutorService executorService = Executors.newFixedThreadPool(10);
        for (int i = 0; i < 100; i++) {
            executorService.execute(runnable);
        }
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        executorService.shutdown();
        return start;
    }
    synchronized void add() {
        for (int i = 0; i < 100000; i++) {
            start++;
        }
    }
}

或者

public class Test {
    private AtomicInteger start = new AtomicInteger(0);
    public static void main(String[] args) {
        List<Integer> res = new ArrayList<>();
        for (int i = 0; i < 10; i++) {
            res.add(new Test().test());
        }
        System.out.println(res);
    }
    private int test() {
        Runnable runnable = () -> add();
        ExecutorService executorService = Executors.newFixedThreadPool(10);
        for (int i = 0; i < 100; i++) {
            executorService.execute(runnable);
        }
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        executorService.shutdown();
        return start.get();
    }
    void add() {
        for (int i = 0; i < 100000; i++) {
            start.addAndGet(1);
        }
    }
}

或者

public class Test {
    private static ReentrantLock lock = new ReentrantLock();
    private int start = 0;

    public static void main(String[] args) {
        List<Integer> res = new ArrayList<>();
        for (int i = 0; i < 10; i++) {
            res.add(new Test().test());
        }
        System.out.println(res);
    }

    private int test() {
        Runnable runnable = () -> add();
        ExecutorService executorService = Executors.newFixedThreadPool(10);
        for (int i = 0; i < 100; i++) {
            executorService.execute(runnable);
        }
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        executorService.shutdown();
        return start;
    }

    void add() {
        lock.lock();
        try {
            for (int i = 0; i < 100000; i++) {
                start++;
            }
        } finally {
            lock.unlock();
        }
    }
}

如果覺得不錯(cuò)的話,請(qǐng)幫忙點(diǎn)個(gè)贊唄涡戳。

以上


掃描下面的二維碼结蟋,關(guān)注我的公眾號(hào) 103Tech, 點(diǎn)關(guān)注渔彰,不迷路嵌屎。

103Tech

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市恍涂,隨后出現(xiàn)的幾起案子宝惰,更是在濱河造成了極大的恐慌,老刑警劉巖再沧,帶你破解...
    沈念sama閱讀 219,490評(píng)論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件尼夺,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡产园,警方通過查閱死者的電腦和手機(jī)汞斧,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,581評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來什燕,“玉大人,你說我怎么就攤上這事竞端∈杭矗” “怎么了?”我有些...
    開封第一講書人閱讀 165,830評(píng)論 0 356
  • 文/不壞的土叔 我叫張陵事富,是天一觀的道長技俐。 經(jīng)常有香客問我,道長统台,這世上最難降的妖魔是什么雕擂? 我笑而不...
    開封第一講書人閱讀 58,957評(píng)論 1 295
  • 正文 為了忘掉前任,我火速辦了婚禮贱勃,結(jié)果婚禮上井赌,老公的妹妹穿的比我還像新娘。我一直安慰自己贵扰,他們只是感情好仇穗,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,974評(píng)論 6 393
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著戚绕,像睡著了一般纹坐。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上舞丛,一...
    開封第一講書人閱讀 51,754評(píng)論 1 307
  • 那天耘子,我揣著相機(jī)與錄音果漾,去河邊找鬼。 笑死谷誓,一個(gè)胖子當(dāng)著我的面吹牛绒障,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播片林,決...
    沈念sama閱讀 40,464評(píng)論 3 420
  • 文/蒼蘭香墨 我猛地睜開眼端盆,長吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來了费封?” 一聲冷哼從身側(cè)響起焕妙,我...
    開封第一講書人閱讀 39,357評(píng)論 0 276
  • 序言:老撾萬榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎弓摘,沒想到半個(gè)月后焚鹊,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,847評(píng)論 1 317
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡韧献,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,995評(píng)論 3 338
  • 正文 我和宋清朗相戀三年末患,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片锤窑。...
    茶點(diǎn)故事閱讀 40,137評(píng)論 1 351
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡璧针,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出渊啰,到底是詐尸還是另有隱情探橱,我是刑警寧澤,帶...
    沈念sama閱讀 35,819評(píng)論 5 346
  • 正文 年R本政府宣布绘证,位于F島的核電站隧膏,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏嚷那。R本人自食惡果不足惜胞枕,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,482評(píng)論 3 331
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望魏宽。 院中可真熱鬧腐泻,春花似錦、人聲如沸湖员。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,023評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽娘摔。三九已至窄坦,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背鸭津。 一陣腳步聲響...
    開封第一講書人閱讀 33,149評(píng)論 1 272
  • 我被黑心中介騙來泰國打工彤侍, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人逆趋。 一個(gè)月前我還...
    沈念sama閱讀 48,409評(píng)論 3 373
  • 正文 我出身青樓盏阶,卻偏偏與公主長得像,于是被迫代替她去往敵國和親闻书。 傳聞我的和親對(duì)象是個(gè)殘疾皇子名斟,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,086評(píng)論 2 355

推薦閱讀更多精彩內(nèi)容

  • Java內(nèi)存區(qū)域 Java虛擬機(jī)在運(yùn)行程序時(shí)會(huì)把其自動(dòng)管理的內(nèi)存劃分為以上幾個(gè)區(qū)域,每個(gè)區(qū)域都有的用途以及創(chuàng)建銷毀...
    架構(gòu)師springboot閱讀 1,773評(píng)論 0 5
  • 九種基本數(shù)據(jù)類型的大小魄眉,以及他們的封裝類砰盐。(1)九種基本數(shù)據(jù)類型和封裝類 (2)自動(dòng)裝箱和自動(dòng)拆箱 什么是自動(dòng)裝箱...
    關(guān)瑋琳linSir閱讀 1,887評(píng)論 0 47
  • 本文出自 Eddy Wiki ,轉(zhuǎn)載請(qǐng)注明出處:http://eddy.wiki/interview-java.h...
    eddy_wiki閱讀 2,130評(píng)論 0 14
  • Java SE 基礎(chǔ): 封裝坑律、繼承岩梳、多態(tài) 封裝: 概念:就是把對(duì)象的屬性和操作(或服務(wù))結(jié)合為一個(gè)獨(dú)立的整體,并盡...
    Jayden_Cao閱讀 2,110評(píng)論 0 8
  • 第2章 java并發(fā)機(jī)制的底層實(shí)現(xiàn)原理 Java中所使用的并發(fā)機(jī)制依賴于JVM的實(shí)現(xiàn)和CPU的指令晃择。 2.1 vo...
    kennethan閱讀 1,432評(píng)論 0 2