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

資訊專欄INFORMATION COLUMN

Java 線程和 volatile 解釋

ytwman / 482人閱讀

摘要:支持多線程,中創(chuàng)建線程的方式有兩種繼承類,重寫方法。多線程編程很常見的情況下是希望多個(gè)線程共享資源,通過多個(gè)線程同時(shí)消費(fèi)資源來提高效率,但是新手一不小心很容易陷入一個(gè)編碼誤區(qū)。所以,在進(jìn)行多線程編程的時(shí)候一定要留心多個(gè)線程是否共享資源。

文章首發(fā)于 http://jaychen.cc
作者 JayChen

最近開始學(xué)習(xí) Java,所以記錄一些 Java 的知識點(diǎn)。這篇是一些關(guān)于 Java 線程的文章。

Java 支持多線程,Java 中創(chuàng)建線程的方式有兩種:

繼承 Thread 類,重寫 run 方法。

實(shí)現(xiàn) Runnable 接口,實(shí)現(xiàn) run 方法。

// 繼承 Thread 類
class ThreadDemo extends Thread {

    @Override
    public void run() {
        System.out.println("一個(gè)簡單的例子就需要這么多代碼...");
    }
}



// 實(shí)現(xiàn) Runnable 接口
class RunnableDemo implements Runnable {
    public void run() {
        System.out.println("一個(gè)簡單的例子就需要這么多代碼...");
    }
}


public class Main {
    public static void main(String[] strings) {
        
        // 繼承 Thread 類
        Thread thread = new ThreadDemo();
        thread.start();
        
        // 實(shí)現(xiàn) Runnable 接口
        Thread again = new Thread(new RunnableDemo());
        again.start();
    }
}

通過調(diào)用 start 函數(shù)可以啟動(dòng)有一個(gè)新的線程,并且執(zhí)行 run 方法中的邏輯。這里可以引出一個(gè)很容易被問道的面試題:

Thread 類中 start 函數(shù)和 run 函數(shù)有什么區(qū)別。

最明顯的區(qū)別在于,直接調(diào)用 run 方法并不會啟動(dòng)一個(gè)新的線程來執(zhí)行,而是調(diào)用 run 方法的線程直接執(zhí)行。只有調(diào)用 start 方法才會啟動(dòng)一個(gè)新的線程來執(zhí)行。

引入線程的目的是為了使得多個(gè)線程可以在多個(gè) CPU 上同時(shí)運(yùn)行,提高多核 CPU 的利用率。

多線程編程很常見的情況下是希望多個(gè)線程共享資源,通過多個(gè)線程同時(shí)消費(fèi)資源來提高效率,但是新手一不小心很容易陷入一個(gè)編碼誤區(qū)。

class ThreadDemo extends Thread {
    private int i = 3;
    @Override
    public void run() {
        i--;
        System.out.println(i);
    }
}

public class Main {
    public static void main(String[] strings) {
        Thread thread = new ThreadDemo();
        thread.start();
        Thread thread1 = new ThreadDemo();
        thread1.start();
        Thread thread2 = new ThreadDemo();
        thread2.start();
    }
}

上面的實(shí)例代碼,希望通過 3 個(gè)線程同時(shí)執(zhí)行 i--; 操作,使得最終 i 的值為 0,但是結(jié)果不如人意,3 次輸出的結(jié)果都為 2。這是因?yàn)樵?main 方法中創(chuàng)建的三個(gè)線程都獨(dú)自持有一個(gè) i ,我們的目的一應(yīng)該是 3 個(gè)線程共享一個(gè) i。

public class Main {
    public static void main(String[] strings) {
        DemoRunnable demoRunnable = new DemoRunnable();
        new Thread(demoRunnable).start();
        new Thread(demoRunnable).start();
        new Thread(demoRunnable).start();
    }
}

class DemoRunnable implements Runnable {

    private int i= 3;

    @Override
    public void run() {
        i--;
        System.out.println(i);
    }
}

使用上面的代碼才有可能使得 i 最終的結(jié)果為0。所以,在進(jìn)行多線程編程的時(shí)候一定要留心多個(gè)線程是否共享資源。

Volatile

如果你運(yùn)氣好,執(zhí)行上面的代碼發(fā)現(xiàn),有時(shí)候三次 i--; 的結(jié)果也不一定是 0。這種怪異的現(xiàn)象需要從 JVM 的內(nèi)存模型說起。

當(dāng) Java 啟動(dòng)了多個(gè)線程分布在不同的 CPU 上執(zhí)行邏輯,JVM 為了提高性能,會把在內(nèi)存中的數(shù)據(jù)拷貝一份到 CPU 的寄存器中,使得 CPU 讀取數(shù)據(jù)更快。很明顯,這種提高性能的做法會使得 Thread1 中對 i 的修改不能馬上反應(yīng)到 Thread2 中。

下面例子可以明顯的體現(xiàn)出這個(gè)問題。

public class Main {
    static int NEXT_IN_LINE = 0;
    
    public static void main(String[] args) throws Exception {
        new ThreadA().start();
        new ThreadB().start();
    }

    static class ThreadA extends Thread {
        @Override
        public void run() {
            while (true) {
                if (NEXT_IN_LINE >= 4) {
                    break;
                }
            }
            System.out.println("in CustomerInLine...." + NEXT_IN_LINE);
        }
    }

    static class ThreadB extends Thread {
        @Override
        public void run() {
            while (NEXT_IN_LINE < 10) {
                System.out.println("in Queue ..." + NEXT_IN_LINE++);
                try {
                    Thread.sleep(200);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }
    }
}

上面的代碼中,ThreadA 線程進(jìn)入死循環(huán)一直到 NEXT_IN_LINE 的值為 4 才退出,ThreadB 線程不停的對 NEXT_IN_LINE++ 操作。然而執(zhí)行代碼發(fā)現(xiàn) ThreadA 沒有輸出 in CustomerInLine...." + NEXT_IN_LINE,而是一直處于死循環(huán)狀態(tài)。這個(gè)例子可以很明顯的驗(yàn)證:"JVM 會把線程共享的變量拷貝到寄存器中以提高效率" 的說法。

那么,怎么才能避免這種優(yōu)化給編程帶來的困擾?這里要引出一個(gè)內(nèi)存可見性 的概念。

內(nèi)存可見性指的是一個(gè)線程對共享變量值的修改,能夠及時(shí)地被其他線程看到。

為了實(shí)現(xiàn)內(nèi)存可見性,Java 引入了 volatile 的關(guān)鍵字。這個(gè)關(guān)鍵字的作用在于,當(dāng)使用 volatile 修改了某個(gè)變量,那么 JVM 就不會對該變量進(jìn)行優(yōu)化,即意味著,不會把該變量拷貝到 CPU 寄存器中,每個(gè)變量對該變量的修改,都會實(shí)時(shí)的反應(yīng)在內(nèi)存中。

針對上面的例子,把 static int NEXT_IN_LINE = 0; 改成 static volatile int NEXT_IN_LINE = 0; 那么執(zhí)行的結(jié)果就如我們所預(yù)料的,在 ThraedB 自增到 NEXT_IN_LINE = 4 的時(shí)候 ThreadA 會跳出死循環(huán)。

指令重排

volatile 還有一個(gè)很好玩的特性:防止指令重排。

首先要明白什么是指令重排?

假設(shè)在 ThreadA 中有

context = loadContext();
inited = true;

ThreadB 中

while(!inited) {
    sleep(100);
}
doSomething(context);

那么,ThreadB 中會在 inited 置位 true 之后執(zhí)行 doSomething 方法,inited 變量的作用就是用來標(biāo)志 context 是否被初始化了。但是實(shí)際上在執(zhí)行 ThreadA 代碼的時(shí)候 JVM 會根據(jù)上下行代碼是否互相關(guān)聯(lián)而決定是否對代碼執(zhí)行順序進(jìn)行重排。這就意味著 CPU 認(rèn)為 ThreadA 中的兩行代碼沒有順序關(guān)聯(lián),于是先執(zhí)行 inited=true 再執(zhí)行 context=loadContext()。如此一來,就會導(dǎo)致 ThreadB 中引用了一個(gè)值為 null 的 context 對象。

使用 volatile 可以避免指令重排。在定義 inited 變量的時(shí)候使用 olatile修飾:volatile boolean inited = false;。 使用 volatile 修飾 inited 之后,JVM 就不會對 inited 相關(guān)的變量進(jìn)行指令重排。

原子性

回到最初的例子。在 volatile 部分我們說過最終的結(jié)果不是輸出 i = 0 的原因是 JVM 拷貝內(nèi)存變量到 CPU 寄存器中導(dǎo)致線程之間沒辦法實(shí)時(shí)更新 i 變量的值導(dǎo)致的,只要使用 volatile 修飾 i 就可以實(shí)現(xiàn)內(nèi)存可見性,可以使得結(jié)果輸出 i = 0。但是實(shí)際上,即使使用了 volatile 之后,還是有可能的導(dǎo)致 i != 0 的結(jié)果。

輸出 i != 0 的結(jié)果是由于 i++; 操作并非為原子性操作。

什么是原子性操作?簡單來說就是一個(gè)操作不能再分解。i++ 操作實(shí)際上分為 3 步:

讀取 i 變量的值。

增加 i 變量的值。

把新的值寫到內(nèi)存中。

那么,假設(shè) ThraedA 在執(zhí)行第 2 步之后,ThreadB 讀取了 i 變量的值,這時(shí)候還未被 ThreadA 更新,讀取的仍是舊的值,之后 ThreadA 寫入了新的值。這種情況下就會導(dǎo)致 i 在某個(gè)時(shí)刻被修改多次。

解決這種問題需要用到 synchronized。但是這里不打算對 synchronized 進(jìn)行討論。這里指出一個(gè)很容易被誤解的概念:volatile 能夠?qū)崿F(xiàn)內(nèi)存可見性和避免指令重排,但是不能實(shí)現(xiàn)原子性。

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

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

相關(guān)文章

  • Java中的Volatile關(guān)鍵字

    摘要:變量可見性問題的關(guān)鍵字保證了多個(gè)線程對變量值變化的可見性。只要一個(gè)線程需要首先讀取一個(gè)變量的值,基于這個(gè)值生成一個(gè)新值,則一個(gè)關(guān)鍵字不足以保證正確的可見性。 Java的volatile關(guān)鍵字用于標(biāo)記一個(gè)Java變量為在主存中存儲。更確切的說,對volatile變量的讀取會從計(jì)算機(jī)的主存中讀取,而不是從CPU緩存中讀取,對volatile變量的寫入會寫入到主存中,而不只是寫入到CPU緩存...

    JohnLui 評論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 評論0 收藏0
  • 理解java Volatile 關(guān)鍵字

    摘要:最近在看多線程相關(guān),看到這篇來自大神關(guān)于關(guān)鍵字的講解感覺非常詳細(xì)易懂,特此轉(zhuǎn)載一下。如果對增加聲明則所有線程對的寫都會立即刷新到主存中,而且所有對的讀也都直接從主存中去讀。 最近在看java多線程相關(guān),看到這篇來自大神Jakob Jenkov關(guān)于Volatile關(guān)鍵字的講解感覺非常詳細(xì)易懂,特此轉(zhuǎn)載一下。原文鏈接:http://tutorials.jenkov.com/j... 內(nèi)存可...

    ConardLi 評論0 收藏0
  • java】知識系譜-基礎(chǔ)篇-線程-volatile

    摘要:每個(gè)會緩存主存的共享變量,從而提高處理效率。為當(dāng)前緩存行加入緩存一致性協(xié)議。任何修改,其他線程是可見的。修飾的變量還是會緩存的,只是通過一系列處理保證了所有線程看到這個(gè)變量的值是一致的 java并發(fā)編程實(shí)戰(zhàn)對volatile的解釋就是:當(dāng)一個(gè)域聲明為valatile類型后,編譯器與運(yùn)行時(shí)會監(jiān)視這個(gè)變量:它是共享的,而且對它的操作不會與其他的內(nèi)存操作一起被重排序。volatile變量不會...

    _ivan 評論0 收藏0
  • 線程學(xué)習(xí)筆記(1):volatilesynchronized

    摘要:今天開始整理學(xué)習(xí)多線程的知識,談?wù)勛钪匾膬蓚€(gè)關(guān)鍵字和。但是這樣一個(gè)過程比較慢,在使用多線程的時(shí)候就會出現(xiàn)問題。有序性有序性是指多線程執(zhí)行結(jié)果的正確性。這種機(jī)制在多線程中會出現(xiàn)問題,因此可以通過來禁止重排。 今天開始整理學(xué)習(xí)多線程的知識,談?wù)勛钪匾膬蓚€(gè)關(guān)鍵字:volatile和synchronized。 一、三個(gè)特性 1、原子性 所謂原子性操作就是指這些操作是不可中斷的,要么執(zhí)行過程...

    jk_v1 評論0 收藏0

發(fā)表評論

0條評論

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