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

資訊專欄INFORMATION COLUMN

淺談并發(fā)及Java實(shí)現(xiàn) (一) - 并發(fā)設(shè)計(jì)的三大原則

gecko23 / 3261人閱讀

摘要:并發(fā)設(shè)計(jì)的三大原則原子性原子性對(duì)共享變量的操作相對(duì)于其他線程是不可干擾的,即其他線程的執(zhí)行只能在該原子操作完成后或開始前執(zhí)行。發(fā)現(xiàn)兩個(gè)線程運(yùn)行結(jié)束后的值為。這就是在多線程情況下要求程序執(zhí)行的順序按照代碼的先后順序執(zhí)行的原因之一。

并發(fā)設(shè)計(jì)的三大原則 原子性

原子性:對(duì)共享變量的操作相對(duì)于其他線程是不可干擾的,即其他線程的執(zhí)行只能在該原子操作完成后或開始前執(zhí)行。

通過一個(gè)小例子理解

public class Main {

    private static Integer a = 0;

    public static void main(String[] args) {
        ExecutorService pool = Executors.newFixedThreadPool(50);
        for (int i = 0; i < 50; i++) {
            pool.submit(() -> {
                a = a + 1;
            });
        }
        pool.shutdown();
        
        //等待線程全部結(jié)束
        while(!pool.isTerminated());
        System.out.println(a);
    }
}

這里創(chuàng)建了一個(gè)包含50個(gè)線程的線程池,并讓每個(gè)線程執(zhí)行一次自增的操作,最后等待全部線程執(zhí)行結(jié)束之后打印a的值。
理論上,這個(gè)a的值應(yīng)該是50吧,但實(shí)際運(yùn)行發(fā)現(xiàn)并不是如此,而且多次運(yùn)行的結(jié)果不一樣。

分析一下原因,在多線程的情況下,a = a + 1這一條語句是可能被多個(gè)線程同時(shí)執(zhí)行或交替執(zhí)行的,而這條語句本身分為3個(gè)步驟,讀取a的值,a的值+1,寫回a。
假設(shè)現(xiàn)在a的值為1,線程A和線程B正在執(zhí)行。線程A讀取a得值為1,并將a得值+1(線程A內(nèi)a的值目前依舊為1),此時(shí)線程B讀取a得值為1,將a值+1,寫回a,此時(shí)a為2,線程A再次運(yùn)行,將剛才+1后的a值(2)寫回a。
發(fā)現(xiàn)兩個(gè)線程運(yùn)行結(jié)束后a的值為2。

以一個(gè)表格描述運(yùn)行的過程。

線程A 線程B a
讀取a 讀取a 1
a + 1 a + 1,寫回結(jié)果 2
寫回結(jié)果 2

這一現(xiàn)象發(fā)生的原因,正是因?yàn)閍 = a + 1其實(shí)是由多個(gè)步驟所構(gòu)成的,在一個(gè)線程操作的過程中,其他線程也可以進(jìn)行操作,所以發(fā)生了非預(yù)期的錯(cuò)誤結(jié)果。

因此,若能保證一個(gè)線程在執(zhí)行操作共享變量的時(shí)候,其他線程不能操作,即不能干擾的情況下,就能保證程序正常的運(yùn)行了,這就是原子性。

可見性

可見性:當(dāng)一個(gè)線程修改了狀態(tài),其他的線程能夠看到改變。

了解過計(jì)算機(jī)組成原理的應(yīng)該知道,為了緩解CPU過高的執(zhí)行速度和內(nèi)存過低的讀取速度的矛盾,CPU內(nèi)置了緩存功能,能夠存儲(chǔ)近期訪問過的數(shù)據(jù),若需要再次操作這些數(shù)據(jù),只需要從緩存中讀取即可,大大減少了內(nèi)存I/O的時(shí)間。

(此處應(yīng)當(dāng)有JVM的內(nèi)存結(jié)構(gòu)分析,待添加)

但此時(shí)就產(chǎn)生了一個(gè)問題,在多處理器的情況下,若對(duì)同一個(gè)內(nèi)存區(qū)域進(jìn)行操作,就會(huì)在多個(gè)處理器緩存中存在該內(nèi)存區(qū)域的拷貝。但每個(gè)處理器對(duì)結(jié)果的操作并不能對(duì)其他處理器可見,因?yàn)楦鱾€(gè)處理器都在讀取自己的緩存區(qū)域,這就造成了緩存不一致的情況。

同樣以一個(gè)小例子理解

public class Main {
    private static Boolean ready = false;
    private static Integer number = 0;

    public static void main(String[] args) throws InterruptedException {
        new Thread(() -> {
            while (!ready) ;
            System.out.println(number);
        }).start();
        Thread.sleep(100);
        number = 42;
        ready = true;
        System.out.println("Main Thread Over !");
    }
}

這里ready初始化為false,創(chuàng)建一個(gè)線程,持續(xù)監(jiān)測(cè)ready的值,直到為true后打印number的結(jié)果。
主線程則在創(chuàng)建完線程后給ready和number重新賦值。

運(yùn)行之后發(fā)現(xiàn),程序打印出了Main Thread Over !意味著主線程結(jié)束,此時(shí)ready和number應(yīng)該已經(jīng)被賦值,但等待很久之后發(fā)現(xiàn)還是沒有正常打印出number的值。

因?yàn)檫@里在主線程讓線程暫停了一段時(shí)間,保證子線程先運(yùn)行,此時(shí)子線程讀到的內(nèi)存中的ready為false,并拷貝至自身的緩存,當(dāng)主線程運(yùn)行時(shí),修改了ready的值,而子線程并不知道這一事件的發(fā)生,依舊在使用緩沖中的值。這正是因?yàn)槎嗑€程下緩存的不一致,即可見性問題。

如果有興趣的朋友可以將Thread.sleep(100);這句取消,看看結(jié)果,分析一下原因。
有序性

有序性:程序執(zhí)行的順序按照代碼的先后順序執(zhí)行。

可能有同學(xué)看到這一條不是很理解,而且這個(gè)相關(guān)的例子也很難給出,因?yàn)榇嬖诤艽蟮碾S機(jī)性。
首先理解一下,為什么會(huì)有這一條,難道程序的執(zhí)行順序還不是按照我寫的代碼的順序嗎?

其實(shí)還真不一定是。

上面講到,每個(gè)處理器都會(huì)有一個(gè)高速緩存,在程序運(yùn)行中,更多次數(shù)的命中緩存,往往意味著更高效率的運(yùn)行,而緩存的空間實(shí)際是很小的,可能時(shí)常需要讓出空間為新變量使用。針對(duì)這一點(diǎn),很多編譯器內(nèi)置了一個(gè)優(yōu)化,通過不影響程序的運(yùn)行結(jié)果,調(diào)整部分代碼的位置,使得高速緩存的利用率提升。

例如

Integer a,b;
a = a + 1; //(1)
b = b - 3; //(2)
a = a + 1; //(3)

如果處理器的緩存空間很小,只能存下一個(gè)變量,那么將第(3)句放置(1),(2)句之間,是不是緩存多使用了一次,而且沒有改變程序的運(yùn)行結(jié)果。這就是重排序問題,當(dāng)然重排序提升的不僅僅是緩存利用率,還有其他很多的方面。

到這里,可能會(huì)有疑問,不是說保證不影響程序運(yùn)行結(jié)果才會(huì)有重排序發(fā)生嗎,為什么還要考慮這一點(diǎn)。

重排序遵守一個(gè)happens-before原則,而這個(gè)原則實(shí)則并沒有對(duì)多線程交替的情況進(jìn)行考慮,因?yàn)檫@太復(fù)雜,考慮多線程的交替性還要進(jìn)行重排序而不影響運(yùn)行結(jié)果的最好辦法,就是不排序 :-)

happens-before原則

同一個(gè)線程中的每個(gè)Action都happens-before于出現(xiàn)在其后的任何一個(gè)Action。

對(duì)一個(gè)監(jiān)視器的解鎖happens-before于每一個(gè)后續(xù)對(duì)同一個(gè)監(jiān)視器的加鎖。

對(duì)volatile字段的寫入操作happens-before于每一個(gè)后續(xù)的同一個(gè)字段的讀操作。

Thread.start()的調(diào)用會(huì)happens-before于啟動(dòng)線程里面的動(dòng)作。

Thread中的所有動(dòng)作都happens-before于其他線程檢查到此線程結(jié)束或者Thread.join()中返回或者Thread.isAlive()==false。

一個(gè)線程A調(diào)用另一個(gè)另一個(gè)線程B的interrupt()都happens-before于線程A發(fā)現(xiàn)B被A中斷(B拋出異?;蛘逜檢測(cè)到B的isInterrupted()或者interrupted())。

一個(gè)對(duì)象構(gòu)造函數(shù)的結(jié)束happens-before與該對(duì)象的finalizer的開始

如果A動(dòng)作happens-before于B動(dòng)作,而B動(dòng)作happens-before與C動(dòng)作,那么A動(dòng)作happens-before于C動(dòng)作。

那么,多線程下的重排序會(huì)怎么樣影響程序的結(jié)果呢?還是拿上一個(gè)例子來講

public class Main {
    private static volatile Boolean ready = false;
    private static volatile Integer number = 0;

    public static void main(String[] args) throws InterruptedException {
        new Thread(() -> {
            while (!ready) ;
            System.out.println(number);
        }).start();
        number = 42; //(1)
        ready = true; //(2)
        System.out.println("Main Thread Over !");
    }
}

注意此處刪除了線程休眠的代碼。

這里我們假設(shè)理想的情況,現(xiàn)在整個(gè)程序已經(jīng)滿足了可見性(此處使用了volatile,具體原理可見續(xù)文),而此時(shí)發(fā)生了重排序,將(1)(2)兩行的內(nèi)容進(jìn)行了交換,子線程開始了運(yùn)行,并持續(xù)檢測(cè)ready中。主線程執(zhí)行,由于發(fā)生了重排序,(2)將先會(huì)執(zhí)行,此時(shí)子線程看到ready變?yōu)榱藅rue,之后打印出number的值,此時(shí),number的值為0,而預(yù)期的結(jié)果應(yīng)該是42。

這就是在多線程情況下要求程序執(zhí)行的順序按照代碼的先后順序執(zhí)行的原因之一。

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

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

相關(guān)文章

  • 【推薦】最新200篇:技術(shù)文章整理

    摘要:作為面試官,我是如何甄別應(yīng)聘者的包裝程度語言和等其他語言的對(duì)比分析和主從復(fù)制的原理詳解和持久化的原理是什么面試中經(jīng)常被問到的持久化與恢復(fù)實(shí)現(xiàn)故障恢復(fù)自動(dòng)化詳解哨兵技術(shù)查漏補(bǔ)缺最易錯(cuò)過的技術(shù)要點(diǎn)大掃盲意外宕機(jī)不難解決,但你真的懂?dāng)?shù)據(jù)恢復(fù)嗎每秒 作為面試官,我是如何甄別應(yīng)聘者的包裝程度Go語言和Java、python等其他語言的對(duì)比分析 Redis和MySQL Redis:主從復(fù)制的原理詳...

    BicycleWarrior 評(píng)論0 收藏0
  • 【推薦】最新200篇:技術(shù)文章整理

    摘要:作為面試官,我是如何甄別應(yīng)聘者的包裝程度語言和等其他語言的對(duì)比分析和主從復(fù)制的原理詳解和持久化的原理是什么面試中經(jīng)常被問到的持久化與恢復(fù)實(shí)現(xiàn)故障恢復(fù)自動(dòng)化詳解哨兵技術(shù)查漏補(bǔ)缺最易錯(cuò)過的技術(shù)要點(diǎn)大掃盲意外宕機(jī)不難解決,但你真的懂?dāng)?shù)據(jù)恢復(fù)嗎每秒 作為面試官,我是如何甄別應(yīng)聘者的包裝程度Go語言和Java、python等其他語言的對(duì)比分析 Redis和MySQL Redis:主從復(fù)制的原理詳...

    tommego 評(píng)論0 收藏0
  • 后臺(tái)開發(fā)常問面試題集錦(問題搬運(yùn)工,附鏈接)

    摘要:基礎(chǔ)問題的的性能及原理之區(qū)別詳解備忘筆記深入理解流水線抽象關(guān)鍵字修飾符知識(shí)點(diǎn)總結(jié)必看篇中的關(guān)鍵字解析回調(diào)機(jī)制解讀抽象類與三大特征時(shí)間和時(shí)間戳的相互轉(zhuǎn)換為什么要使用內(nèi)部類對(duì)象鎖和類鎖的區(qū)別,,優(yōu)缺點(diǎn)及比較提高篇八詳解內(nèi)部類單例模式和 Java基礎(chǔ)問題 String的+的性能及原理 java之yield(),sleep(),wait()區(qū)別詳解-備忘筆記 深入理解Java Stream流水...

    spacewander 評(píng)論0 收藏0
  • 后臺(tái)開發(fā)常問面試題集錦(問題搬運(yùn)工,附鏈接)

    摘要:基礎(chǔ)問題的的性能及原理之區(qū)別詳解備忘筆記深入理解流水線抽象關(guān)鍵字修飾符知識(shí)點(diǎn)總結(jié)必看篇中的關(guān)鍵字解析回調(diào)機(jī)制解讀抽象類與三大特征時(shí)間和時(shí)間戳的相互轉(zhuǎn)換為什么要使用內(nèi)部類對(duì)象鎖和類鎖的區(qū)別,,優(yōu)缺點(diǎn)及比較提高篇八詳解內(nèi)部類單例模式和 Java基礎(chǔ)問題 String的+的性能及原理 java之yield(),sleep(),wait()區(qū)別詳解-備忘筆記 深入理解Java Stream流水...

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

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

0條評(píng)論

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