如何理解JAVA的多線程-創(chuàng)新互聯(lián)

這篇文章主要介紹“如何理解JAVA的多線程”,在日常操作中,相信很多人在如何理解JAVA的多線程問題上存在疑惑,小編查閱了各式資料,整理出簡單好用的操作方法,希望對大家解答”如何理解JAVA的多線程”的疑惑有所幫助!接下來,請跟著小編一起來學(xué)習吧!

創(chuàng)新互聯(lián)公司專注為客戶提供全方位的互聯(lián)網(wǎng)綜合服務(wù),包含不限于網(wǎng)站設(shè)計制作、成都網(wǎng)站設(shè)計、山城網(wǎng)絡(luò)推廣、成都小程序開發(fā)、山城網(wǎng)絡(luò)營銷、山城企業(yè)策劃、山城品牌公關(guān)、搜索引擎seo、人物專訪、企業(yè)宣傳片、企業(yè)代運營等,從售前售中售后,我們都將竭誠為您服務(wù),您的肯定,是我們大的嘉獎;創(chuàng)新互聯(lián)公司為所有大學(xué)生創(chuàng)業(yè)者提供山城建站搭建服務(wù),24小時服務(wù)熱線:13518219792,官方網(wǎng)址:muchs.cn

多線程

計算機存儲體系

要想明白數(shù)據(jù)一致性問題,要先縷下計算機存儲結(jié)構(gòu),從本地磁盤到主存到CPU緩存,也就是從硬盤到內(nèi)存,到CPU。一般對應(yīng)的程序的操作就是從數(shù)據(jù)庫查數(shù)據(jù)到內(nèi)存然后到CPU進行計算。這個描述有點粗,下邊畫個圖。

如何理解JAVA的多線程

業(yè)內(nèi)畫這個圖一般都是畫的金字塔型狀,為了證明是我自己畫的我畫個長方型的(其實我不會畫金字塔)。

CPU多個核心和內(nèi)存之間為了保證內(nèi)部數(shù)據(jù)一致性還有一個緩存一致性協(xié)議(MESI),MESI其實就是指令狀態(tài)中的首字母。M(Modified)修改,E(Exclusive)獨享、互斥,S(Shared)共享,I(Invalid)無效。然后再看下邊這個圖。

如何理解JAVA的多線程

太細的狀態(tài)流轉(zhuǎn)就不作描述了,扯這么多主要是為了說明白為什么會有數(shù)據(jù)一致性問題,就是因為有這么多級的緩存,CPU的運行并不是直接操作內(nèi)存而是先把內(nèi)存里邊的數(shù)據(jù)讀到緩存,而內(nèi)存的讀和寫操作的時候就會造成不一致的問題。解決一致性問題怎么辦呢,兩個思路。

  1. 鎖住總線,操作時鎖住總線,這樣效率非常低,所以考慮第二個思路。

  2. 緩存一致性,每操作一次通知(一致性協(xié)議MESI),(但多線程的時候還是會有問題,后文講)

JAVA內(nèi)存模型

上邊稍微扯了一下存儲體系是為了在這里寫一下JAVA內(nèi)存模型。

Java虛擬機規(guī)范中試圖定義一種Java內(nèi)存模型(java Memory Model) 來屏蔽掉各種硬件和操作系統(tǒng)的內(nèi)存訪問差異,以實現(xiàn)讓Java程序在各種平臺下都能達到一致的內(nèi)存訪問效果。

內(nèi)存模型是內(nèi)存和線程之間的交互、規(guī)則。與編譯器有關(guān),有并發(fā)有關(guān),與處理器有關(guān)。

Java內(nèi)存模型的主要目標是定義程序中各個變量的訪問規(guī)則,即在虛擬機中將變量存儲到內(nèi)存和從內(nèi)存中取出變量這樣的底層細節(jié)。此處的變量與Java編程中所說的變量有所區(qū)別,它包括 了實例字段、靜態(tài)字段和構(gòu)成數(shù)組對象的元素,但不包括局部變量與方法參數(shù),因為后者是線程私有的,不會被共享,自然就不會存在競爭問題。為了獲得較好的執(zhí)行效能,Java內(nèi)存模型并沒有限制執(zhí)行引擎使用處理器特定寄存器或緩存來和主內(nèi)存進行交互,也沒有限制即時編譯器進行調(diào)整代碼執(zhí)行順序這類優(yōu)化措施。

Java內(nèi)存模型規(guī)定了所有的變量都存儲在主內(nèi)存中。每條線程還有自己的工作內(nèi)存,線程的工作內(nèi)存中保存了該線程使用到的變量的主內(nèi)存副本拷貝,線程對變量的所有操作(讀取,賦值等 )都必需在工作內(nèi)存中進行,而不能直接讀寫主內(nèi)存中的變量。不同的線程之間也無法直接訪問對方工作內(nèi)存中的變量,線程間變量值的傳遞均需要通過主內(nèi)存來完成。

這里所說的主內(nèi)存、工作內(nèi)存和Java內(nèi)存區(qū)域中的Java堆、棧、方法區(qū)等并不是同一個層次的內(nèi)存劃分,這兩者基本上是沒有關(guān)系的。  如果兩者一定要勉強對應(yīng)起來,那從變量、主內(nèi)存、工作內(nèi)存的定義來看,主內(nèi)存對應(yīng)Java堆中的對象實例數(shù)據(jù)部分 ,而工作內(nèi)存則對應(yīng)于虛擬機棧中的部分區(qū)域。從更底層次上說,主內(nèi)存就是直接對應(yīng)于物理硬件的內(nèi)存,而為了獲取更好的運行速度,虛擬機可能會讓工作內(nèi)存優(yōu)先存儲于寄存器和高速緩存中,因為程序運行時主要訪問讀寫的是工作內(nèi)存。

如何理解JAVA的多線程

前邊說的都是和內(nèi)存有關(guān)的內(nèi)容,其實多線程有關(guān)系的還有指令重排序,指令重排序也會造成在多線程訪問下結(jié)束和想的不一樣的情況。大段的介紹就不寫了要不篇幅太長了(JVM那里書里邊有)。主要就是在CPU執(zhí)行指令的時候會進行執(zhí)行順序的優(yōu)化。畫個圖看一下吧。

如何理解JAVA的多線程

具體理論后文再寫先來點干貨,直接上代碼,一看就明白。

public class HappendBeforeTest {
    int a = 0;
    int b = 0;
    public static void main(String[] args) {
        HappendBeforeTest test = new HappendBeforeTest();
        Thread threada = new Thread() {
            @Override
            public void run() {
                test.a = 1;
                System.out.println("b=" + test.b);
            }
        };
        Thread threadb = new Thread() {
            @Override
            public void run() {
                test.b = 1;
                System.out.println("a=" + test.a);
            }
        };
        threada.start();
        threadb.start();
    }
}

猜猜有可能輸出什么?多選

A:a=0,b=1
B:a=1,b=0
C:a=0,b=0
D:a=1,b=1

上邊這段代碼不太好調(diào),然后我稍微改造了一下。

public class HappendBeforeTest {
    static int a = 0;
    static int b = 0;
    static int x = 0;
    static int y = 0;
    public static void shortWait(long interval) {
        long start = System.nanoTime();
        long end;
        do {
            end = System.nanoTime();
        }
        while (start + interval >= end);
    }
    public static void main(String[] args) throws InterruptedException {
        for (; ; ) {
            Thread threada = new Thread() {
                @Override
                public void run() {
                    a = 1;
                    x = b;
                }
            };
            Thread threadb = new Thread() {
                @Override
                public void run() {
                    b = 1;
                    y = a;
                }
            };
            Thread starta = new Thread() {
                @Override
                public void run() {
                    // 由于線程threada先啟動
                    //下面這句話讓它等一等線程startb
                    shortWait(100);
                    threada.start();
                }
            };
            Thread startb = new Thread() {
                @Override
                public void run() {
                    threadb.start();
                }
            };
            starta.start();
            startb.start();
            starta.join();
            startb.join();
            threada.join();
            threadb.join();
            a = 0;
            b = 0;
            System.out.print("x=" + x);
            System.out.print("y=" + y);
            if (x == 0 && y == 0) {
                break;
            }
            x = 0;
            y = 0;
            System.out.println();
        }
    }
}

這段代碼,a和b初始值為0,然后兩個線程同時啟動分別設(shè)置a=1,x=b和b=1,y=a。這個代碼里邊的starta和startb線程完全是為了讓threada 和threadb 兩個線程盡量同時啟動而加的,里邊只是分別調(diào)用了threada 和threadb 兩個線程。然后無限循環(huán)只要x和y 不同時等于0就初始化所有值繼續(xù)循環(huán),直到x和y都是0的時候break。你猜猜會不會break。

結(jié)果看截圖

如何理解JAVA的多線程

因為我沒有記錄循環(huán)次數(shù),不知道循環(huán)了幾次,然后觸發(fā)了條件break了。從代碼上看,在輸出A之前必然會把B設(shè)置成1,在輸出B之前必然會把A設(shè)置為1。那為什么會出現(xiàn)同時是零的情況呢。這就很有可能是指令被重排序了。

指令重排序簡單了說是就兩行以上不相干的代碼在執(zhí)行的時候有可能先執(zhí)行的不是第一條。也就是執(zhí)行順序會被優(yōu)化。

如何判斷你寫的代碼執(zhí)行順序會不會被優(yōu)化,要看代碼之間有沒有Happens-before關(guān)系。Happens-before就是不無需任何干涉就可以保證有有序執(zhí)行,由于篇幅限制Happens-before就不在這里多做介紹。

下面簡單介紹一下java里邊的一個關(guān)鍵字volatile。volatile簡單來說就是來解決重排序問題的。對一個volatile變量的寫,一定happen-before后續(xù)對它的讀。也就是你在寫代碼的時候不希望你的代碼被重排序就使用volatile關(guān)鍵字。volatile還解決了內(nèi)存可見性問題,在執(zhí)行執(zhí)行的時候一共有8條指令lock(鎖定)、read(讀取)、load(載入)、use(使用)、assign(賦值)、store(存儲)、write(寫入)、unlock(解鎖)(篇幅限制具體指令內(nèi)容自行查詢,看下圖大概有個了解)。

如何理解JAVA的多線程

volatile主要是對其中4條指令做了處理。如下圖

如何理解JAVA的多線程

也就是把 load和use關(guān)聯(lián)執(zhí)行,把assign和store關(guān)聯(lián)執(zhí)行。眾所周知有l(wèi)oad必需有read現(xiàn)在load又和use關(guān)聯(lián)也就是要在緩存中要use的時候就必須要load要load就必需要read。通俗講就是要use(使用)一個變量的時候必需load(載入),要載入的時候必需從主內(nèi)存read(讀?。┻@樣就解決了讀的可見性。下面看寫操作它是把assign和store做了關(guān)聯(lián),也就是在assign(賦值)后必需store(存儲)。store(存儲)后write(寫入)。也就是做到了給一個變量賦值的時候一串關(guān)聯(lián)指令直接把變量值寫到主內(nèi)存。就這樣通過用的時候直接從主內(nèi)存取,在賦值到直接寫回主內(nèi)存做到了內(nèi)存可見性。

無鎖編程

我在網(wǎng)上看到大部分寫多線程的時候都會寫到鎖,AQS和線程池。由于網(wǎng)文太多本文就不多做介紹。下面簡單寫一寫CAS。

CAS是一個比較魔性的操作,用的好可以讓你的代碼更優(yōu)雅更高效。它就是無鎖編程的核心。

CAS書上是這么介紹的:“CAS即Compare and Swap,是JDK提供的非阻塞原子性操作,它通過硬件保證了比較-更新的原子性”。他是非阻塞的還是原子性,也就是說這玩意效率更高。還是通過硬件保證的說明這玩意更可靠。

如何理解JAVA的多線程

從上圖可以看出,在cas指令修改變量值的時候,先要進行值的判斷,如果值和原來的值相等說明還沒有被其它線程改過,則執(zhí)行修改,如果被改過了,則不修改。在java里邊java.util.concurrent.atomic包下邊的類都使用了CAS操作。最常用的方法就是compareAndSet。其底層是調(diào)用的Unsafe類的compareAndSwap方法。

到此,關(guān)于“如何理解JAVA的多線程”的學(xué)習就結(jié)束了,希望能夠解決大家的疑惑。理論與實踐的搭配能更好的幫助大家學(xué)習,快去試試吧!若想繼續(xù)學(xué)習更多相關(guān)知識,請繼續(xù)關(guān)注創(chuàng)新互聯(lián)-成都網(wǎng)站建設(shè)公司網(wǎng)站,小編會繼續(xù)努力為大家?guī)砀鄬嵱玫奈恼拢?/p>

網(wǎng)站標題:如何理解JAVA的多線程-創(chuàng)新互聯(lián)
文章轉(zhuǎn)載:http://muchs.cn/article6/degsog.html

成都網(wǎng)站建設(shè)公司_創(chuàng)新互聯(lián),為您提供企業(yè)建站、網(wǎng)站制作、品牌網(wǎng)站制作、域名注冊、外貿(mào)建站、云服務(wù)器

廣告

聲明:本網(wǎng)站發(fā)布的內(nèi)容(圖片、視頻和文字)以用戶投稿、用戶轉(zhuǎn)載內(nèi)容為主,如果涉及侵權(quán)請盡快告知,我們將會在第一時間刪除。文章觀點不代表本網(wǎng)站立場,如需處理請聯(lián)系客服。電話:028-86922220;郵箱:631063699@qq.com。內(nèi)容未經(jīng)允許不得轉(zhuǎn)載,或轉(zhuǎn)載時需注明來源: 創(chuàng)新互聯(lián)

h5響應(yīng)式網(wǎng)站建設(shè)