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

資訊專欄INFORMATION COLUMN

Java并發(fā)編程之指令重排序

microcosm1994 / 2219人閱讀

摘要:安全性小結(jié)我們上邊介紹了原子性操作內(nèi)存可見性以及指令重排序三個(gè)在多線程執(zhí)行過程中會(huì)影響到安全性的問題。

指令重排序

如果說內(nèi)存可見性問題已經(jīng)讓你抓狂了,那么下邊的這個(gè)指令重排序的事兒估計(jì)就要罵娘了~這事兒還得從一段代碼說起:

public class Reordering {

    private static boolean flag;
    private static int num;

    public static void main(String[] args) {
        Thread t1 = new Thread(new Runnable() {

            @Override
            public void run() {
                while (!flag) {
                    Thread.yield();
                }

                System.out.println(num);
            }
        }, "t1");
        t1.start();
        num = 5;
        flag = true;
    }
}

需要注意到flag并不是一個(gè)volatile變量,也就是說它存在內(nèi)存可見性問題,但是即便如此,num = 5也是寫在flag = true的前邊的,等到t1線程檢測到了flag值的變化,num值的變化應(yīng)該是早于flag值刷新到主內(nèi)存的,所以線程t1最后的輸出結(jié)果肯定是5?。?!

no!no!no! 輸出的結(jié)果也可能是0,也就是說flag = true可能先于num = 5執(zhí)行,有沒有亮瞎你的狗眼~ 這些代碼最后都會(huì)變成機(jī)器能識(shí)別的二進(jìn)制指令,我們把這種指令不按書寫順序執(zhí)行的情況稱為指令重排序。大多數(shù)現(xiàn)代處理器都會(huì)采用將指令亂序執(zhí)行的方法,在條件允許的情況下,直接運(yùn)行當(dāng)前有能力立即執(zhí)行的后續(xù)指令,避開獲取下一條指令所需數(shù)據(jù)時(shí)造成的等待。通過亂序執(zhí)行的技術(shù),處理器可以大大提高執(zhí)行效率。

Within-Thread As-If-Serial Semantics

既然存在指令重排序這種現(xiàn)象,為什么我們之前寫代碼從來沒感覺到呢?到了多線程這才發(fā)現(xiàn)問題?

指令重排序不是隨便排,一個(gè)一萬行的程序直接把最后一行當(dāng)成第一行就給執(zhí)行那不就逆天了了么,指令重排序是需要遵循代碼依賴情況的。比如下邊幾行代碼:

int i = 0, b = 0;
i = i + 5;  //指令1
i = i*2;  //指令2
b = b + 3;  //指令3

對于上邊標(biāo)注的3個(gè)指令來說,指令2是對指令1有依賴的,所以指令2不能被排到指令1之前執(zhí)行。但是指令3指令1指令2都沒有關(guān)系,所以指令3可以被排在指令1之前,或者指令1指令2中間或者指令2后邊執(zhí)行都可以~ 這樣在單線程中執(zhí)行這段代碼的時(shí)候,最終結(jié)果和沒有重排序的執(zhí)行結(jié)果是一樣的,所以這種重排序有著Within-Thread As-If-Serial Semantics的含義,翻譯過來就是線程內(nèi)表現(xiàn)為串行的語義。

但是這種指令重排序單線程中沒有任何問題的,但是在多線程中,就引發(fā)了我們上邊在執(zhí)行flag = true后,num的值仍然不能確定是0還是5

抑制重排序

在多線程并發(fā)編程的過程中,執(zhí)行重排序有時(shí)候會(huì)造成錯(cuò)誤的后果,比如一個(gè)線程在main線程中調(diào)用setFlag(true)的前邊修改了某些程序配置項(xiàng),而在t1線程里需要用到這些配置項(xiàng),所以會(huì)造成配置缺失的錯(cuò)誤。但是java給我們提供了一些抑制指令重排序的方式。

同步代碼抑制指令重排序

將需要抑制指令重排序的代碼放入同步代碼塊中:

public class Reordering {

    private static boolean flag;
    private static int num;

    public static void main(String[] args) {
        Thread t1 = new Thread(new Runnable() {

            @Override
            public void run() {
                while (!getFlag()) {
                    Thread.yield();
                }

                System.out.println(num);
            }
        }, "t1");
        t1.start();
        num = 5;
        setFlag(true);
    }

    public synchronized static void setFlag(boolean flag) {
        Reordering.flag = flag;
    }

    public synchronized static boolean getFlag() {
        return flag;
    }
}

在獲取鎖的時(shí)候,它前邊的操作必須已經(jīng)執(zhí)行完成,不能和同步代碼塊重排序;在釋放鎖的時(shí)候,同步代碼塊中的代碼必須全部執(zhí)行完成,不能和同步代碼塊后邊的代碼重排序。

加了鎖之后,num=5就不能和flag=true的代碼進(jìn)行重排序了,所以在線程2中看到的num值肯定是5,而不會(huì)是0嘍~

雖然抑制重排序可以保證多線程程序按照我們期望的執(zhí)行順序進(jìn)行執(zhí)行,但是它抑制了處理器對指令執(zhí)行的優(yōu)化,原來能并行執(zhí)行的指令現(xiàn)在只能串行執(zhí)行,會(huì)導(dǎo)致一定程度的性能下降,所以加鎖只能保證在執(zhí)行同步代碼塊時(shí),它之前的代碼已經(jīng)執(zhí)行完成,在同步代碼塊執(zhí)行完成之前,代碼塊后邊的代碼是不能執(zhí)行的,也就是只保證加鎖前、加鎖中、加鎖后這三部分的執(zhí)行時(shí)序,但是同步代碼塊之前的代碼可以重排序,同步代碼塊中的代碼可以重排序,同步代碼塊之后的代碼也可以進(jìn)行重排序,在保證執(zhí)行順序的基礎(chǔ)上,盡最大可能讓性能得到提升,比方說下邊這段代碼:

int i = 1;
int j = 2;
synchronized (Reordering.class) {
    int m = 3;
    int n = 4;
}
int x = 5;
int y = 6;

它的一個(gè)執(zhí)行時(shí)序可能是:

volatile變量抑制指令重排序

還是那句老話,加鎖會(huì)導(dǎo)致競爭同一個(gè)鎖的線程阻塞,造成線程切換,代價(jià)比較大,volatile變量也提供了一些抑制指令重排序的語義,上邊的程序可以改成這樣:

public class Reordering {

    private static volatile boolean flag;
    private static int num;

    public static void main(String[] args) {
        Thread t1 = new Thread(new Runnable() {

            @Override
            public void run() {
                while (!flag) {
                    Thread.yield();
                }

                System.out.println(num);
            }
        });
        t1.start();
        num = 5;
        flag = true;
    }
}
``
也就是把``flag``聲明為``volatile變量``,這樣也能起到抑制重排序的效果,``volatile變量``具體抑制重排序的規(guī)則如下:

1. volatile寫之前的操作不會(huì)被重排序到volatile寫之后。
2. volatile讀之后的操作不會(huì)被重排序到volatile讀之前。
3. 前邊是volatile寫,后邊是volatile讀,這兩個(gè)操作不能重排序。
![圖片描述][3]
除了這三條規(guī)定以外,其他的操作可以由處理器按照自己的特性進(jìn)行重排序,換句話說,就是怎么執(zhí)行著快,就怎么來。比如說:

flag = true;
num = 5;
``
volatile變量之后進(jìn)行普通變量的寫操作,那就可以重排序嘍,直到遇到一條volatile讀或者有執(zhí)行依賴的代碼才會(huì)阻止重排序的過程。

final變量抑制指令重排序

在java語言中,用final修飾的字段被賦予了一些特殊的語義,它可以阻止某些重排序,具體的規(guī)則就這兩條:

在構(gòu)造方法內(nèi)對一個(gè)final字段的寫入,與隨后把這個(gè)被構(gòu)造對象的引用賦值給一個(gè)引用變量,這兩個(gè)操作之間不能重排序。

初次讀一個(gè)包含final字段對象的引用,與隨后初次讀這個(gè)final字段,這兩個(gè)操作不能重排序。

可能大家看的有些懵逼,趕緊寫代碼理解一下:

public class FinalReordering {

    int i;
    final int j;

    static FinalReordering obj;

    public FinalReordering() {
        i = 1;
        j = 2;
    }

    public static void write() {
        obj = new FinalReordering();
    }

    public static void read() {
        FinalReordering finalReordering = FinalReordering.obj;
        int a = finalReordering.i;
        int b = finalReordering.j;
    }
}

我們假設(shè)有一個(gè)線程執(zhí)行write方法,另一個(gè)線程執(zhí)行read方法。

先看一下對final字段進(jìn)行寫操作時(shí),不同線程執(zhí)行write方法和read方法的一種可能情況是:

從上圖中可以看出,普通的字段可能在構(gòu)造方法完成之后才被真正的寫入值,所以另一個(gè)線程在訪問這個(gè)普通變量的時(shí)候可能讀到了0,這顯然是不符合我們的預(yù)期的。但是final字段的賦值不允許被重排序到構(gòu)造方法完成之后,所以在把該字段所在對象的引用賦值出去之前,final字段肯定是被賦值過了,也就是說這兩個(gè)操作不能被重排序。

再來看一下初次讀取final字段的情況,下邊是不同線程執(zhí)行write方法和read方法的一種可能情況:

從上圖可以看出,普通字段的讀取操作可能被重排序到讀取該字段所在對象引用前邊,自然會(huì)得到NullPointerException異常嘍,但是對于final字段,在讀final字段之前,必須保證它前邊的讀操作都執(zhí)行完成,也就是說必須先進(jìn)行該字段所在對象的引用的讀取,再讀取該字段,也就是說這兩個(gè)操作不能進(jìn)行重排序。

值得注意的是,讀取對象引用與讀取該對象的字段是存在間接依賴的關(guān)系的,對象引用都沒有被賦值,還讀個(gè)錘子對象的字段嘍,一般的處理器默認(rèn)是不會(huì)重排序這兩個(gè)操作的,可是有一些為了性能不顧一切的處理器,比如alpha處理器,這種處理器是可能把這兩個(gè)操作進(jìn)行重排序的,所以這個(gè)規(guī)則就是給這種處理器貼身設(shè)計(jì)的~ 也就是說對于final字段,不管在什么處理器上,都得先進(jìn)行對象引用的讀取,再進(jìn)行final字段的讀取。但是并不保證在所有處理器上,對于對象引用讀取和普通字段讀取的順序是有序的。

安全性小結(jié)

我們上邊介紹了原子性操作、內(nèi)存可見性以及指令重排序三個(gè)在多線程執(zhí)行過程中會(huì)影響到安全性的問題。

synchronized可以把三個(gè)問題都解決掉,但是伴隨著這種萬能特性,是多線程在競爭同一個(gè)鎖的時(shí)候會(huì)造成線程切換,導(dǎo)致線程阻塞,這個(gè)對性能的影響是非常大的。

volatile不能保證一系列操作的原子性,但是可以保證對于一個(gè)變量的讀取和寫入是原子性的,一個(gè)線程對某個(gè)volatile變量的寫入是可以立即對其他線程可見的,另外,它還可以禁止處理器對一些指令執(zhí)行的重排序。

final變量依靠它的禁止重排序規(guī)則,保證在使用過程中的安全性。一旦被賦值成功,它的值在之后程序執(zhí)行過程中都不會(huì)改變,也不存在所謂的內(nèi)存可見性問題。

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

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

相關(guān)文章

  • 并發(fā)編程的藝術(shù)

    摘要:假設(shè)不發(fā)生編譯器重排和指令重排,線程修改了的值,但是修改以后,的值可能還沒有寫回到主存中,那么線程得到就是很自然的事了。同理,線程對于的賦值操作也可能沒有及時(shí)刷新到主存中。線程的最后操作與線程發(fā)現(xiàn)線程已經(jīng)結(jié)束同步。 很久沒更新文章了,對隔三差五過來刷更新的讀者說聲抱歉。 關(guān)于 Java 并發(fā)也算是寫了好幾篇文章了,本文將介紹一些比較基礎(chǔ)的內(nèi)容,注意,閱讀本文需要一定的并發(fā)基礎(chǔ)。 本文的...

    curlyCheng 評(píng)論0 收藏0
  • Java 并發(fā)編程(學(xué)習(xí))

    摘要:并發(fā)編程的挑戰(zhàn)并發(fā)編程的目的是為了讓程序運(yùn)行的更快,但是,并不是啟動(dòng)更多的線程就能讓程序最大限度的并發(fā)執(zhí)行。的實(shí)現(xiàn)原理與應(yīng)用在多線程并發(fā)編程中一直是元老級(jí)角色,很多人都會(huì)稱呼它為重量級(jí)鎖。 并發(fā)編程的挑戰(zhàn) 并發(fā)編程的目的是為了讓程序運(yùn)行的更快,但是,并不是啟動(dòng)更多的線程就能讓程序最大限度的并發(fā)執(zhí)行。如果希望通過多線程執(zhí)行任務(wù)讓程序運(yùn)行的更快,會(huì)面臨非常多的挑戰(zhàn):(1)上下文切換(2)死...

    NervosNetwork 評(píng)論0 收藏0
  • Java多線程可見性談Happens-Before原則

    摘要:本文會(huì)先闡述在并發(fā)編程中解決的問題多線程可見性,然后再詳細(xì)講解原則本身。所以與內(nèi)存之間的高速緩存就是導(dǎo)致線程可見性問題的一個(gè)原因。原則上面討論了中多線程共享變量的可見性問題及產(chǎn)生這種問題的原因。 Happens-Before是一個(gè)非常抽象的概念,然而它又是學(xué)習(xí)Java并發(fā)編程不可跨域的部分。本文會(huì)先闡述Happens-Before在并發(fā)編程中解決的問題——多線程可見性,然后再詳細(xì)講解H...

    MyFaith 評(píng)論0 收藏0
  • Java并發(fā)編程-原子操作

    摘要:這個(gè)規(guī)則比較好理解,無論是在單線程環(huán)境還是多線程環(huán)境,一個(gè)鎖處于被鎖定狀態(tài),那么必須先執(zhí)行操作后面才能進(jìn)行操作。線程啟動(dòng)規(guī)則獨(dú)享的方法先行于此線程的每一個(gè)動(dòng)作。 1. 指令重排序 關(guān)于指令重排序的概念,比較復(fù)雜,不好理解。我們從一個(gè)例子分析: public class SimpleHappenBefore { /** 這是一個(gè)驗(yàn)證結(jié)果的變量 */ private st...

    SillyMonkey 評(píng)論0 收藏0
  • Java并發(fā)編程:從根源上解析volatile關(guān)鍵字的實(shí)現(xiàn)

    摘要:并發(fā)編程關(guān)鍵字解析解析概覽內(nèi)存模型的相關(guān)概念并發(fā)編程中的三個(gè)概念內(nèi)存模型深入剖析關(guān)鍵字使用關(guān)鍵字的場景內(nèi)存模型的相關(guān)概念緩存一致性問題。事實(shí)上,這個(gè)規(guī)則是用來保證程序在單線程中執(zhí)行結(jié)果的正確性,但無法保證程序在多線程中執(zhí)行的正確性。 Java并發(fā)編程:volatile關(guān)鍵字解析 1、解析概覽 內(nèi)存模型的相關(guān)概念 并發(fā)編程中的三個(gè)概念 Java內(nèi)存模型 深入剖析volatile關(guān)鍵字 ...

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

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

0條評(píng)論

microcosm1994

|高級(jí)講師

TA的文章

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