Java并發(fā)編程之可見性类缤、原子性和有序性解析

古語有云,天上的一天邻吭,地上的一年餐弱,當(dāng)年玉帝妹子私自下凡間,與楊天佑結(jié)為夫婦囱晴,有一天玉帝突然想起膏蚓,妹妹呢,咋好幾天沒見到她了畸写,雖然在天上只是幾天時(shí)間驮瞧,而在凡間玉帝妹子和楊君都有了仨孩子啦,這也才有了后來二郎真君劈山救母的故事枯芬。

劈山救母

言歸正傳论笔,其實(shí)在計(jì)算機(jī)的世界里同樣存在這樣的矛盾,那就是CPU千所、內(nèi)存和I/O設(shè)備之間速度差異狂魔。根據(jù)木桶效應(yīng),即一個(gè)木桶能裝多少水取決于它最短的那塊木板淫痰,I/O設(shè)備的瓶頸制約著軟件的性能最楷。

為了應(yīng)對這個(gè)問題,從計(jì)算機(jī)體系結(jié)構(gòu)層面、操作系統(tǒng)層面和程序編譯層面都有相應(yīng)的優(yōu)化措施:
1)計(jì)算機(jī)體系層面籽孙,CPU增加緩存烈评,均衡了CPU與內(nèi)存之間的速度差異;
2)操作系統(tǒng)層面蚯撩,引入了進(jìn)程和線程础倍,以時(shí)分復(fù)用的方式均衡CPU與I/O設(shè)備之間的速度差異;
3)編譯程序優(yōu)化指令執(zhí)行次序胎挎,使得緩存能夠得到更加合理地利用。

然而沒有一勞永逸的方法忆家,在享受這些便利的時(shí)候犹菇,我們也要承受它給我們帶來的困擾,這些優(yōu)化就是很多并發(fā)編程中詭異問題的根源所在芽卿,主要表現(xiàn)為三個(gè)方面:可見性問題揭芍、原子性問題和有序性問題。

1. 可見性問題

可見性卸例,即一個(gè)線程對共享變量的修改称杨,另一個(gè)線程能夠立即看到,在多核時(shí)代筷转,每個(gè)CPU都有自己的緩存姑原,如下圖所示,線程1操作CPU01的緩存呜舒,線程2操作CPU02的緩存锭汛,顯然線程1對共享變量的操作對于線程2來說就不具備可見性。

可見性問題

我們可以通過如下程序驗(yàn)證這個(gè)問題

public class CurrencyTest {
    int count = 0;
    public void countAdd() {
        for(int i = 0; i < 10000; i++)
        count+=1;
    }
    public static void main(String[] args) throws InterruptedException {
        CurrencyTest currencyTest = new CurrencyTest();
        Thread thread1 = new Thread(() -> currencyTest.countAdd());
        Thread thread2 = new Thread(() -> currencyTest.countAdd());
        thread1.start();
        thread2.start();
        thread1.join();
        thread2.join();
        System.out.println(currencyTest.count);
    }
}

運(yùn)行上面的代碼袭蝗,其結(jié)果是10000到20000之間的一個(gè)隨機(jī)數(shù)唤殴,這就是可見性問題引起的,每個(gè)CPU中都有共享變量count到腥,自己玩自己的朵逝,每個(gè)線程都是根據(jù)各自CPU中的緩存值操作,最后就會(huì)出現(xiàn)數(shù)據(jù)不一致的問題乡范。

2. 原子性問題

即使是在單核系統(tǒng)中配名,仍然能夠邊上網(wǎng)邊聽歌,這就得益于多線程時(shí)分復(fù)用的出現(xiàn)篓足,當(dāng)年Unix也是因?yàn)檫@個(gè)而名揚(yáng)天下的段誊。它解決了I/O等待時(shí)間長阻塞線程而浪費(fèi)CPU資源的問題,多線程時(shí)分復(fù)用的原理如下圖所示栈拖,將CPU劃分為時(shí)間片连舍,在一個(gè)時(shí)間片內(nèi),如果一個(gè)進(jìn)程進(jìn)行一個(gè) IO 操作,例如讀個(gè)文件索赏,這個(gè)時(shí)候該進(jìn)程可以把自己標(biāo)記為“休眠狀態(tài)”并出讓 CPU的使用權(quán)盼玄,待文件讀進(jìn)內(nèi)存,操作系統(tǒng)會(huì)把這個(gè)休眠的進(jìn)程喚醒潜腻,喚醒后的進(jìn)程就有機(jī)會(huì)重新獲得 CPU 的使用權(quán)了埃儿。

原子性問題

Java中的并發(fā)編程是基于多線程的,會(huì)涉及到任務(wù)切換(任務(wù)切換通常指的就是線程切換)融涣,線程切換也是詭異Bug的源頭之一童番,線程的切換可以發(fā)生在程序運(yùn)行的任何一條指令,注意這里強(qiáng)調(diào)的是指令而不是Java中的一條代碼威鹿,例如我們熟悉的i++操作就是???三條指令完成的剃斧,
1)把變量i的值加載到CPU的寄存器中;
2)在寄存器中執(zhí)行+1操作忽你;
3)將結(jié)果寫入內(nèi)存幼东,緩存機(jī)制可能導(dǎo)致結(jié)果寫入CPU緩存而不是內(nèi)存中。
由于存在線程的切換科雳,i++的操作可能被中斷根蟹,引起數(shù)據(jù)不一致的問題,我們把一個(gè)或者多個(gè)操作在CPU中執(zhí)行的過程中不被中斷的特性稱為原子性糟秘。

3. 有序性問題

編譯階段的指令重排序會(huì)導(dǎo)致順序性問題简逮,從硬件架構(gòu)上來看,指令重排序是指CPU采用了允許將多條指令不按程序規(guī)定的順序分開發(fā)送各相應(yīng)電路單元處理的方式蚌堵,但并不是說指令任意排序买决,指令重排序不能影響正確的執(zhí)行結(jié)果。周志明老師《深入理解Java虛擬機(jī)》一書中總結(jié)道:Java程序中天然的有序性可以總結(jié)為一句話吼畏,如果在本線程內(nèi)觀察督赤,所有的操作都是有序的;如果在一個(gè)線程中觀察另一個(gè)線程泻蚊,所有的操作都是無序的躲舌,前半句指的是線程內(nèi)表現(xiàn)為串行的語義,后半句指的是指令重排序現(xiàn)象和工作內(nèi)存與貯存同步延遲的現(xiàn)象性雄。舉個(gè)例子來說明没卸,單例模式的雙重檢查鎖

public class SingletonDemo {

    private /** volatile*/ static SingletonDemo instance;

    public static SingletonDemo getInstance() {
        if (instance == null) {
            synchronized (SingletonDemo.class) {
                if (null == instance) {
                    instance = new SingletonDemo();
                }
            }
        }
        return instance;
    }
}

這里為什么不能缺少volatile關(guān)鍵字呢?主要在于instance = new Instance()這個(gè)語句實(shí)際上包含了三個(gè)操作:
1)分配對象內(nèi)存空間秒旋;
2)初始化對象约计;
3)設(shè)置instance指向剛分配的內(nèi)存地址

前文中提到一個(gè)線程內(nèi)看其他線程中的指令執(zhí)行順序可能是亂序的,有可能是如下順序:
1)分配對象內(nèi)存迁筛;
2)設(shè)置instance指向剛分配的內(nèi)存煤蚌;
3)初始化對象
那么其他線程可能取得的是沒有初始化的對象,出現(xiàn)詭異的并發(fā)bug。

總結(jié)

本文主要分析了并發(fā)編程中詭異bug的三個(gè)來源尉桩,可見性問題筒占、原子性問題和有序性問題。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末蜘犁,一起剝皮案震驚了整個(gè)濱河市翰苫,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌这橙,老刑警劉巖奏窑,帶你破解...
    沈念sama閱讀 223,207評論 6 521
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異屈扎,居然都是意外死亡良哲,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 95,455評論 3 400
  • 文/潘曉璐 我一進(jìn)店門助隧,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人滑沧,你說我怎么就攤上這事并村。” “怎么了滓技?”我有些...
    開封第一講書人閱讀 170,031評論 0 366
  • 文/不壞的土叔 我叫張陵哩牍,是天一觀的道長。 經(jīng)常有香客問我令漂,道長膝昆,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 60,334評論 1 300
  • 正文 為了忘掉前任叠必,我火速辦了婚禮荚孵,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘纬朝。我一直安慰自己收叶,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 69,322評論 6 398
  • 文/花漫 我一把揭開白布共苛。 她就那樣靜靜地躺著判没,像睡著了一般。 火紅的嫁衣襯著肌膚如雪隅茎。 梳的紋絲不亂的頭發(fā)上澄峰,一...
    開封第一講書人閱讀 52,895評論 1 314
  • 那天,我揣著相機(jī)與錄音辟犀,去河邊找鬼俏竞。 笑死,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的胞此。 我是一名探鬼主播臣咖,決...
    沈念sama閱讀 41,300評論 3 424
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼漱牵!你這毒婦竟也來了夺蛇?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 40,264評論 0 277
  • 序言:老撾萬榮一對情侶失蹤酣胀,失蹤者是張志新(化名)和其女友劉穎刁赦,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體闻镶,經(jīng)...
    沈念sama閱讀 46,784評論 1 321
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡甚脉,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,870評論 3 343
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了铆农。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片牺氨。...
    茶點(diǎn)故事閱讀 40,989評論 1 354
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖墩剖,靈堂內(nèi)的尸體忽然破棺而出猴凹,到底是詐尸還是另有隱情,我是刑警寧澤岭皂,帶...
    沈念sama閱讀 36,649評論 5 351
  • 正文 年R本政府宣布郊霎,位于F島的核電站,受9級特大地震影響爷绘,放射性物質(zhì)發(fā)生泄漏书劝。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 42,331評論 3 336
  • 文/蒙蒙 一土至、第九天 我趴在偏房一處隱蔽的房頂上張望购对。 院中可真熱鬧,春花似錦毙籽、人聲如沸洞斯。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,814評論 0 25
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽烙如。三九已至,卻和暖如春毅否,著一層夾襖步出監(jiān)牢的瞬間亚铁,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,940評論 1 275
  • 我被黑心中介騙來泰國打工螟加, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留徘溢,地道東北人吞琐。 一個(gè)月前我還...
    沈念sama閱讀 49,452評論 3 379
  • 正文 我出身青樓,卻偏偏與公主長得像然爆,于是被迫代替她去往敵國和親站粟。 傳聞我的和親對象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,995評論 2 361

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

  • 【韓喜文2019.07.20星期六】 好展館讓天下沒有賣不出去的產(chǎn)品 好展館讓天下沒有不能傳播的文化 日精進(jìn):12...
    韓喜文閱讀 40評論 0 0
  • 只愿能找一個(gè)把我放在第一位的人曾雕,不是說對每個(gè)人都好奴烙,而是對我比對其他人更好,得之剖张,我幸切诀;不得,我命搔弄。我只能努力當(dāng)個(gè)...
    風(fēng)_4922閱讀 158評論 0 0
  • 養(yǎng)育孩子是父母一生的修養(yǎng)。 每當(dāng)土豆出現(xiàn)那么一些不好的行為炫刷,第一時(shí)間反思:是不是我倆哪里沒做好哄芜?想盡自己最大的能力...
    沁雨Amy閱讀 403評論 1 3
  • ———只因你的一點(diǎn)善意,我愿用一生來守護(hù)圃庭。 初讀只覺得虐锄奢,看得難受的虐,以物種間關(guān)系作為整個(gè)故事的線索剧腻,競...
    橙味的小團(tuán)糖閱讀 391評論 0 2
  • 每個(gè)成年人拘央,都不容易,很多時(shí)候迫不得已书在,要戴著面具生活灰伟。看起來很開心的樣子儒旬,看起來活得很好的樣子栏账,其實(shí)只有自己知道...
    千千玲_688c閱讀 331評論 0 2