? ? ? ? 【今天重溫了大神寫的并發(fā)相關(guān)文章】
????????概念定義
? ? ? ? 可見性:一個(gè)線程對共享變量的修改彼妻,另外一個(gè)線程能夠立刻看見,我們稱之為可見性。
? ? ? ? 任務(wù)切換饭于、時(shí)間片:操作系統(tǒng)允許某個(gè)線程執(zhí)行一小段時(shí)間,例如50ms维蒙,過了50ms操作系統(tǒng)就會(huì)重新選擇一個(gè)進(jìn)程來執(zhí)行掰吕,這個(gè)過程叫做“任務(wù)切換”,其中50ms就叫做時(shí)間片颅痊。
? ? ? ? 原子性:一個(gè)或者多個(gè)操作在CPU執(zhí)行的過程中不被中斷的特性稱為原子性殖熟。
????????為什么并發(fā)程序容易出問題呢?
? ? ? ? 1? 緩存導(dǎo)致的可見性問題
? ? ? ? 在單核系統(tǒng)中斑响,所有的線程操作的都是同一個(gè)CPU的緩存菱属,一個(gè)線程對緩存的讀寫钳榨,另一個(gè)線程一定是可見的。假如有共享變量A照皆,如果線程一改變了它的值重绷,那么線程二再訪問A的時(shí)候,得到的一定是被線程一修改過的A的最新值膜毁,這個(gè)過程就是可見性昭卓。
? ? ? ? 在多核系統(tǒng)中,多個(gè)線程在不同的CPU上運(yùn)行瘟滨,這些線程操作的是各自所在CPU的緩存候醒,借用上面的場景來說明,如果CPU1的線程改變了共享變量A的值杂瘸,那么CPU2的線程是感知不到的倒淫,所以不同CPU的線程在操作共享變量時(shí)就不具備可見性,這是目前硬件的客觀條件造成的败玉,算是個(gè)“硬件坑”敌土。
? ??????
? ??????2? 線程切換帶來的原子性問題
? ? ? ? Java并發(fā)程序都是基于多線程的,自然會(huì)涉及到任務(wù)切換运翼。任務(wù)切換的時(shí)機(jī)大多數(shù)是在時(shí)間片結(jié)束的時(shí)候返干,針對高級語言來說,一條命令往往會(huì)對應(yīng)多條CPU指令血淌。
? ? ? ? 操作系統(tǒng)做任務(wù)切換的時(shí)機(jī)矩欠,可以是任何一條cpu指令執(zhí)行完之后。所以CPU能保證的原子操作是cpu指令級別的悠夯,不是高級語言級別癌淮,所以高級語言層面的原子性需要專門處理,很多朋友會(huì)混淆這里沦补。
? ? ? ??
? ??????3? 編譯優(yōu)化帶來的有序性問題
? ? ? ? 有序性導(dǎo)致的問題在Java中有一個(gè)經(jīng)典案例乳蓄,即單例模式的雙重檢查鎖實(shí)現(xiàn)方式。
? ? ? ? 我們先來看段代碼:
? ? ? ? 雙重檢查鎖看上去很完美夕膀,但實(shí)際上getInstance方法并不完美栓袖,問題出在new操作上,我們以為的new操作應(yīng)該是如下順序:
? ? ? ? 1? 分配一塊內(nèi)存M店诗;
? ? ? ? 2? 在內(nèi)存M上初始化Singleton對象裹刮;
? ? ? ? 3? 將M的地址賦值給instance變量。
? ? ? ? 但是實(shí)際上優(yōu)化后的執(zhí)行路徑是這樣的:
? ? ? ? 1??分配一塊內(nèi)存M庞瘸;
? ? ? ? 2??將M的地址賦值給instance變量捧弃;
? ? ? ? 3??在內(nèi)存M上初始化Singleton對象。
? ? ? ? 優(yōu)化后會(huì)導(dǎo)致這樣一個(gè)問題,假設(shè)線程A先執(zhí)行g(shù)etInstance方法违霞,完成指令2之后發(fā)生了線程切換現(xiàn)象嘴办,切換到了線程B上,線程B也在執(zhí)行g(shù)etInstance方法买鸽,結(jié)果發(fā)現(xiàn)instance已經(jīng)有了涧郊,就直接返回instance來使用,但實(shí)際上instance在A線程中沒有真正的做到初始化眼五,因?yàn)闆]完成第三步妆艘,所以線程B在使用instance的時(shí)候一定會(huì)發(fā)生空指針異常。整個(gè)過程如下: