成人国产在线小视频_日韩寡妇人妻调教在线播放_色成人www永久在线观看_2018国产精品久久_亚洲欧美高清在线30p_亚洲少妇综合一区_黄色在线播放国产_亚洲另类技巧小说校园_国产主播xx日韩_a级毛片在线免费

資訊專欄INFORMATION COLUMN

[Java并發(fā)-1]入門(mén):并發(fā)編程Bug的源頭

xiguadada / 3148人閱讀

摘要:所以這情況下,當(dāng)線程操作變量的時(shí)候,變量并不對(duì)線程可見(jiàn)。總結(jié),緩存引發(fā)的可見(jiàn)性問(wèn)題,切換線程帶來(lái)的原子性問(wèn)題,編譯帶來(lái)的有序性問(wèn)題深刻理解這些前因后果,可以診斷大部分并發(fā)的問(wèn)題

背景介紹

如何解決并發(fā)問(wèn)題,首先要理解并發(fā)問(wèn)題的實(shí)際源頭怎么發(fā)生的。

現(xiàn)代計(jì)算機(jī)的不同硬件的運(yùn)行速度是差異很大的,這個(gè)大家應(yīng)該都是知道的。

計(jì)算機(jī)數(shù)據(jù)傳輸運(yùn)行速度上的快慢比較:
CPU > 緩存 > I/O

如何最大化的讓不同速度的硬件可以更好的協(xié)調(diào)執(zhí)行,需要做一些“撮合”的工作

CUP增加了高速緩存來(lái)均衡與緩存間的速度差異

操作系統(tǒng)增加了 進(jìn)程,線程,以分時(shí)復(fù)用CPU,進(jìn)而均衡CPU與I/O的速度差異(當(dāng)?shù)却齀/O的時(shí)候系統(tǒng)切換CPU給系統(tǒng)程序使用)

現(xiàn)代編程語(yǔ)言的編譯器優(yōu)化指令順序,使得緩存能夠合理的利用

上面說(shuō)來(lái)并發(fā)才生問(wèn)題的背景,下面說(shuō)下并發(fā)產(chǎn)生的具體原因是什么

并發(fā)產(chǎn)生的原因 緩存導(dǎo)致的可見(jiàn)性問(wèn)題

先看下單核CPU和緩存之間的關(guān)系:

單核情況下,也是最簡(jiǎn)單的情況,線程A操作寫(xiě)入變量A,這個(gè)變量A的值肯定是被線程B所見(jiàn)的。因?yàn)?個(gè)線程是在一個(gè)CPU上操作,所用的也是同一個(gè)CPU緩存。

這里我們來(lái)定義

一個(gè)線程對(duì)共享變量的修改,另外一個(gè)線程能夠立刻看到,我們稱為 “可見(jiàn)性”

多核CPU時(shí)代下,我們?cè)趤?lái)看下具體情況:

很明顯,多核情況下每個(gè)CPU都有自己的高速緩存,所以變量A的在每個(gè)CPU中可能是不同步的,不一致的。
結(jié)果程A剛好操作來(lái)CPU1的緩存,而線程B也剛好只操作了CPU2的緩存。所以這情況下,當(dāng)線程A操作變量A的時(shí)候,變量并不對(duì)線程B可見(jiàn)。

我們用一段經(jīng)典的代碼說(shuō)明下可見(jiàn)性的問(wèn)題:

    private void add10K() {
        int idx = 0;
        while (idx++ < 100000) {
            count += 1;
        }
    }

    @Test
    public void demo() {

        // 創(chuàng)建兩個(gè)線程,執(zhí)行 add() 操作
        Thread th1 = new Thread(() -> {
            add10K();
        });
        Thread th2 = new Thread(() -> {
            add10K();
        });
        // 啟動(dòng)兩個(gè)線程
        th1.start();
        th2.start();
        // 等待兩個(gè)線程執(zhí)行結(jié)束

        try {
            th1.join();
            th2.join();
        } catch (Exception exc) {

            exc.printStackTrace();
        }

        System.out.println(count);

    }

大家應(yīng)該都知道,答案肯定不是 200000
這就是可見(jiàn)性導(dǎo)致的問(wèn)題,因?yàn)?個(gè)線程讀取變量count時(shí),讀取的都是自己CPU下的高速緩存內(nèi)的緩存值,+1時(shí)也是在自己的高速緩存中。

線程切換帶來(lái)的原子性問(wèn)題

進(jìn)程切換最早是為了提高CPU的使用率而出現(xiàn)的。

比如,50毫米操作系統(tǒng)會(huì)重新選擇一個(gè)進(jìn)程來(lái)執(zhí)行(任務(wù)切換),50毫米成為“時(shí)間片”

早期的操作系統(tǒng)是進(jìn)程間的切換,進(jìn)程間的內(nèi)存空間是不共享的,切換需要切換內(nèi)存映射地址,切換成本大。

而一個(gè)進(jìn)程創(chuàng)建的所有線程,內(nèi)存空間都是共享的。所以現(xiàn)在的操作系統(tǒng)都是基于更輕量的線程實(shí)現(xiàn)切換的,現(xiàn)在我們提到的“任務(wù)切換”都是線程切換。

任務(wù)切換的時(shí)機(jī)大多數(shù)在“時(shí)間片”結(jié)束的時(shí)候。

現(xiàn)在我們使用的基本都是高級(jí)語(yǔ)言,高級(jí)語(yǔ)言的一句對(duì)應(yīng)多條CPU命令,比如 count +=1 至少對(duì)應(yīng)3條CPU命令,指令:

1, 從內(nèi)存加載到CPU的寄存器
2, 在寄存器執(zhí)行 +1
3, 最后,講結(jié)果寫(xiě)回內(nèi)存(緩存機(jī)制導(dǎo)致可能寫(xiě)入的是CPU緩存而不是內(nèi)存)

操作系統(tǒng)做任務(wù)切換,會(huì)在 任意一條CPU指令執(zhí)行完就行切換。所以會(huì)導(dǎo)致問(wèn)題

如圖所示,線程A當(dāng)執(zhí)行完初始化count=0時(shí)候,剛好被線程切換給了線程B。線程B執(zhí)行count+1=1并最后寫(xiě)入值到緩存中,CPU切換回線程A后,繼續(xù)執(zhí)行A線程的count+1=1并再次寫(xiě)入緩存,最后緩存中的count還是為1.
一開(kāi)始我們?nèi)蝿?wù)count+1=1應(yīng)該是一個(gè)不能再被拆開(kāi)的原子操作。

我們把一個(gè)或多個(gè)操作在CPU執(zhí)行過(guò)程中的不被中斷的特性稱為 原子性。

CPU能夠保證的原子性,是CPU指令級(jí)別的。所以高級(jí)語(yǔ)言需要語(yǔ)言層面 保證操作的原子性。

編譯優(yōu)化帶來(lái)的有序性問(wèn)題

有序性。顧名思義,有序性指的是程序按照代碼的先后順序執(zhí)行。

編譯器為了優(yōu)化性能,有時(shí)候會(huì)改變程序中語(yǔ)句的先后順序,例如程序中:a=6;b=7;編譯器優(yōu)化后可能變成b=7;a=6;,在這個(gè)例子中,編譯器調(diào)整了語(yǔ)句的順序,但是不影響程序的最終結(jié)果。不過(guò)有時(shí)候編譯器及解釋器的優(yōu)化可能導(dǎo)致意想不到的 Bug。

Java中的經(jīng)典案例,雙重檢查創(chuàng)建單例對(duì)象;

public class Singleton {

  static Singleton instance;

  static Singleton getInstance(){

    if (instance == null) {

      synchronized(Singleton.class) {

        if (instance == null)

          instance = new Singleton();

        }

    }
    return instance;
  }
}

看似完美的代碼,其實(shí)有問(wèn)題。問(wèn)題就在new上。

想象中 new操作步驟:
1,分配一塊內(nèi)存 M
2,在內(nèi)存M上 初始化對(duì)象
3,把內(nèi)存M地址賦值給 變量

實(shí)際上就行編譯后的順序是:
1,分開(kāi)一塊內(nèi)存 M
2,把內(nèi)存M地址賦值給 變量
3,在 內(nèi)存M上 初始化對(duì)象

優(yōu)化導(dǎo)致的問(wèn)題:

如圖所示,當(dāng)線程A執(zhí)行到第二步的時(shí)候,被線程切換了,這時(shí)候,instance未初始化實(shí)例的對(duì)象,而線程B這時(shí)候執(zhí)行到instance == null ?的判斷中,發(fā)現(xiàn)instance已經(jīng)有“值”了,導(dǎo)致了返回了一個(gè)空對(duì)象的異常。

總結(jié)

1,緩存引發(fā)的可見(jiàn)性問(wèn)題
2,切換線程帶來(lái)的原子性問(wèn)題
3,編譯帶來(lái)的有序性問(wèn)題

深刻理解這些前因后果,可以診斷大部分并發(fā)的問(wèn)題!

文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。

轉(zhuǎn)載請(qǐng)注明本文地址:http://systransis.cn/yun/73764.html

相關(guān)文章

  • Java 并發(fā)學(xué)習(xí)筆記(一)——原子性、可見(jiàn)性、有序性問(wèn)題

    摘要:最后,總結(jié)一下,導(dǎo)致并發(fā)問(wèn)題的三個(gè)源頭分別是原子性一個(gè)線程在執(zhí)行的過(guò)程當(dāng)中不被中斷??梢?jiàn)性一個(gè)線程修改了共享變量,另一個(gè)線程能夠馬上看到,就叫做可見(jiàn)性。 計(jì)算機(jī)的 CPU、內(nèi)存、I/O 設(shè)備的速度一直存在較大的差異,依次是 CPU > 內(nèi)存 > I/O 設(shè)備,為了權(quán)衡這三者的速度差異,主要提出了三種解決辦法: CPU 增加了緩存,均衡和內(nèi)存的速度差異 發(fā)明了進(jìn)程、線程,分時(shí)復(fù)用 CP...

    Chao 評(píng)論0 收藏0
  • Java多線程學(xué)習(xí)(七)并發(fā)編程中一些問(wèn)題

    摘要:因?yàn)槎嗑€程競(jìng)爭(zhēng)鎖時(shí)會(huì)引起上下文切換。減少線程的使用。舉個(gè)例子如果說(shuō)服務(wù)器的帶寬只有,某個(gè)資源的下載速度是,系統(tǒng)啟動(dòng)個(gè)線程下載該資源并不會(huì)導(dǎo)致下載速度編程,所以在并發(fā)編程時(shí),需要考慮這些資源的限制。 最近私下做一項(xiàng)目,一bug幾日未解決,總惶恐。一日頓悟,bug不可怕,怕的是項(xiàng)目不存在bug,與其懼怕,何不與其剛正面。 系列文章傳送門(mén): Java多線程學(xué)習(xí)(一)Java多線程入門(mén) Jav...

    yimo 評(píng)論0 收藏0
  • [Java并發(fā)-10] ReadWriteLock:快速實(shí)現(xiàn)一個(gè)完備緩存

    摘要:此時(shí)線程和會(huì)再有一個(gè)線程能夠獲取寫(xiě)鎖,假設(shè)是,如果不采用再次驗(yàn)證的方式,此時(shí)會(huì)再次查詢數(shù)據(jù)庫(kù)。而實(shí)際上線程已經(jīng)把緩存的值設(shè)置好了,完全沒(méi)有必要再次查詢數(shù)據(jù)庫(kù)。 大家知道了Java中使用管程同步原語(yǔ),理論上可以解決所有的并發(fā)問(wèn)題。那 Java SDK 并發(fā)包里為什么還有很多其他的工具類(lèi)呢?原因很簡(jiǎn)單:分場(chǎng)景優(yōu)化性能,提升易用性 今天我們就介紹一種非常普遍的并發(fā)場(chǎng)景:讀多寫(xiě)少場(chǎng)景。實(shí)際工作...

    nevermind 評(píng)論0 收藏0

發(fā)表評(píng)論

0條評(píng)論

最新活動(dòng)
閱讀需要支付1元查看
<