欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

java多線程:基礎(chǔ)詳解

 更新時(shí)間:2021年08月12日 17:15:39   作者:zuiziyoudexiao  
這篇文章主要介紹了java多線程編程實(shí)例,分享了幾則多線程的實(shí)例代碼,具有一定參考價(jià)值,加深多線程編程的理解還是很有幫助的,需要的朋友可以參考下。

Java內(nèi)存模型

  • Java內(nèi)存模型與Java內(nèi)存結(jié)構(gòu)不同,Java內(nèi)存結(jié)構(gòu)指的是jvm內(nèi)存分區(qū)。Java內(nèi)存模型描述的是多線程環(huán)境下原子性,可見(jiàn)性,有序性的規(guī)則和保障。
  • Java內(nèi)存模型提供了主內(nèi)存和工作內(nèi)存兩種抽象,主內(nèi)存指的是共享區(qū)域 ,工作內(nèi)存指的是線程私有工作空間。
  • 當(dāng)一個(gè)線程訪問(wèn)共享數(shù)據(jù)時(shí),需要先將共享數(shù)據(jù)復(fù)制一份副本到線程的工作內(nèi)存(類(lèi)比操作系統(tǒng)中的高速緩存),然后在工作內(nèi)存進(jìn)行操作,最后再把工作內(nèi)存數(shù)據(jù)覆蓋到主內(nèi)存。主內(nèi)存和工作內(nèi)存交互通過(guò)特定指令完成。
  • 如下為并發(fā)內(nèi)存模型圖

在這里插入圖片描述

多線程環(huán)境下原子性,可見(jiàn)性,有序性分別指的是

  • 原子性:程序執(zhí)行不會(huì)受到線程上下文切換的影響。
  • 可見(jiàn)性:程序執(zhí)行不會(huì)受到CPU緩存影響。
  • 有序性:程序執(zhí)行不會(huì)受到CPU指令并行優(yōu)化的影響。

主內(nèi)存和工作內(nèi)存的交互命令

  • lock:把主內(nèi)存的一個(gè)變量標(biāo)記為一個(gè)線程鎖定狀態(tài)。
  • unlock:把主內(nèi)存中處于鎖定狀態(tài)的變量釋放出來(lái)。
  • read:把主內(nèi)存的變量讀取到線程工作內(nèi)存。
  • load:把工作內(nèi)存的值放入工作內(nèi)存變量副本中。
  • use:把工作內(nèi)存變量的值傳遞給執(zhí)行引擎。
  • assign:把執(zhí)行引擎接收到的值賦值給工作內(nèi)存變量。
  • store:把工作內(nèi)存的值傳送到主內(nèi)存中。
  • write:把工作內(nèi)存的值寫(xiě)入到工作內(nèi)存變量。

內(nèi)存模型的原子性

Java內(nèi)存模型只保證store和write兩個(gè)命令按順序執(zhí)行,但不保證連續(xù)執(zhí)行,因此多個(gè)線程同時(shí)寫(xiě)入共享變量可能出現(xiàn)線程安全問(wèn)題。

諸如i++的操作,首先將主存中的變量i的值拷貝一份拿到線程的本地內(nèi)存,在本地內(nèi)存進(jìn)行自增操作,然后將新的i值寫(xiě)回主存。
但是涉及到多線程環(huán)境下的線程上下文切換就會(huì)出現(xiàn)問(wèn)題,可能線程1將i值拿來(lái)進(jìn)行自增操作,然后還來(lái)不及寫(xiě)回主存,時(shí)間片用完,輪到線程2執(zhí)行,線程2對(duì)i進(jìn)行自減操作,然后輪到線程1時(shí),線程1將上一次的值寫(xiě)回內(nèi)存,就會(huì)將線程2上一步的計(jì)算結(jié)果覆蓋,就會(huì)產(chǎn)生錯(cuò)誤的結(jié)果。

通過(guò)多線程的學(xué)習(xí)我們知道,對(duì)共享數(shù)據(jù)加鎖可以保證操作的原子性,相當(dāng)于i++操作對(duì)應(yīng)底層命令是原子化綁定的,這樣就不會(huì)出現(xiàn)線程安全問(wèn)題,但是會(huì)導(dǎo)致程序性能降低。

內(nèi)存模型的可見(jiàn)性

  • 對(duì)于頻繁從主存取值的操作,JIT可能會(huì)將其進(jìn)行優(yōu)化,以后每次操作不從主存取值,而是從CPU緩存中取值。一旦線程1每次從寄存器取值,那么此時(shí)主存中變量值的變化對(duì)于線程1來(lái)說(shuō)就是不可見(jiàn)的。
  • 如下,子線程是無(wú)法感知主存中flag的修改的,子線程就無(wú)法停止。
    public class Test {
        static boolean flag = true;
        public static void main(String[] args) throws InterruptedException {
            //3秒后線程無(wú)法停止
            new Thread(()->{
                while(flag){
                }
            }).start();
            Thread.sleep(3000);
            System.out.println("flag = false");
            flag =false;
        }
    }
    
  • 有兩種方法可以保證主存中數(shù)據(jù)的可見(jiàn)性,方法1是加鎖。加鎖既可以保證原子性,又可以保證可見(jiàn)性。
    public class Test {
        static boolean flag = true;
        public static void main(String[] args) throws InterruptedException {
                    new Thread(()->{
                        while(flag){
                            synchronized (Test.class){}
                        }
                    }).start();
                    Thread.sleep(3000);
                    System.out.println("flag = false");
                    flag =false;
                }
    }
    
  • 還有一種方法是使用volatile關(guān)鍵字,它可以保證當(dāng)前線程對(duì)共享變量的修改對(duì)另一個(gè)線程是一直可見(jiàn)的。volatile的特殊規(guī)則保證了新值能立即同步到主內(nèi)存,以及每次使用前立即從主內(nèi)存刷新。但是volatile關(guān)鍵字只能保證可見(jiàn)性,不能保證原子性。volatile適用于一個(gè)線程寫(xiě)多個(gè)線程讀的應(yīng)用場(chǎng)景,保證各個(gè)線程可以實(shí)時(shí)感知到其他線程更新的數(shù)據(jù)。
    public class Test {
        static volatile boolean flag = true;
        public static void main(String[] args) throws InterruptedException {
                    new Thread(()->{
                        while(flag){
                        }
                    }).start();
                    Thread.sleep(3000);
                    System.out.println("flag = false");
                    flag =false;
                }
    }
    
  • 對(duì)于多線程同時(shí)操作共享變量的情況,使用volatile關(guān)鍵字依然會(huì)出現(xiàn)線程安全問(wèn)題,因?yàn)樵有詿o(wú)法保證。
public class Test {
    static volatile int a = 0;
    public static void main(String[] args) throws InterruptedException {
        new Thread(()->{
            for(int i=0;i<100000;i++){
                a++;
            }
        }).start();
        new Thread(()->{
            for(int i=0;i<100000;i++){
                a--;
            }
        }).start();
        Thread.sleep(1000);
        System.out.println(a); //不能保證a為0
    }
}

內(nèi)存模型的有序性

有序性是指在單線程環(huán)境中, 程序是按序依次執(zhí)行的。而在多線程環(huán)境中, 程序的執(zhí)行可能因?yàn)橹噶钪嘏哦霈F(xiàn)亂序。

指令重排是指在程序執(zhí)行過(guò)程中, 為了性能考慮, 編譯器和CPU可能會(huì)對(duì)指令重新排序。這種排序(比如兩個(gè)變量的定義順序)不會(huì)影響單線程的結(jié)果,但是會(huì)對(duì)多線程程序產(chǎn)生影響。

比如 a=1 b=2兩條語(yǔ)句就可能發(fā)生指令重排。而 a=1,b=a+1 不會(huì)發(fā)生指令重排。

示例:線程1執(zhí)行f1方法,線程2執(zhí)行f2方法。兩個(gè)線程同時(shí)執(zhí)行,可能發(fā)生如下結(jié)果: f1中發(fā)生指令重排 flag=true先執(zhí)行,a=1后執(zhí)行。線程1先執(zhí)行flag=true,然后輪到線程2執(zhí)行,此時(shí)flag為true,執(zhí)行if語(yǔ)句,i=1。這就是指令重排造成的程序錯(cuò)亂。

class Test{
    int a = 0;
    boolean flag = false;
    public void f1() {
        a = 1;                   
        flag = true;           
    }
    public void f2() {
        if (flag) {                
            int i =  a +1;      
        }
    }
}

可以用volatile修飾flag來(lái)禁用指令重排達(dá)到有序性。

加鎖也可以避免指令重排帶來(lái)的混亂,但是本身并沒(méi)有禁止指令重排,因?yàn)楸WC了原子性,所以即使指令重排在同步代碼塊中依然相當(dāng)于單線程執(zhí)行,也不會(huì)有邏輯上的錯(cuò)誤。

指令重排優(yōu)化的底層原理

一個(gè)指令的執(zhí)行被分成:取指、譯碼、訪存、執(zhí)行、寫(xiě)回 5個(gè)階段。然后,多條指令可以同時(shí)存在于流水線中,同時(shí)被執(zhí)行。
指令流水線并不是串行的,并不會(huì)因?yàn)橐粋€(gè)耗時(shí)很長(zhǎng)的指令在“執(zhí)行”階段呆很長(zhǎng)時(shí)間,而導(dǎo)致后續(xù)的指令阻塞。相反,流水線是并行的,多個(gè)指令可以同時(shí)處于同一個(gè)階段,只要CPU內(nèi)部相應(yīng)的處理部件未被占滿(mǎn)即可。

比如,依次有兩條指令a和b需要執(zhí)行,如果是串行執(zhí)行,它們的執(zhí)行過(guò)程如下

指令a                          指令b
階段1 階段2 階段3 階段4 階段5    階段1 階段2 階段3 階段4 階段5

但是,假如階段2耗時(shí)很長(zhǎng),使用串行的方式就無(wú)法在一個(gè)階段阻塞的時(shí)候去執(zhí)行其他階段。

如下就是流水線的方式來(lái)執(zhí)行,當(dāng)指令a的階段2阻塞時(shí),完全可以去執(zhí)行指令b的階段1,這樣就提高了程序執(zhí)行效率,最大程度利用CPU各個(gè)部件。

指令a                         
階段1 階段2 階段3 階段4 階段5   
指令b
      階段1 階段2 階段3 階段4 階段5

因此指令重排就是對(duì)于一個(gè)線程中的多個(gè)指令,可以在不影響單線程執(zhí)行結(jié)果的前提下,將某些指令的各個(gè)階段進(jìn)行重排序和組合,實(shí)現(xiàn)指令級(jí)并行。

valatile原理

如下,假設(shè)對(duì)變量a用valatile關(guān)鍵字修飾。

valatile int a = 0;

那么,對(duì)變量a的寫(xiě)指令之后都會(huì)插入寫(xiě)屏障,對(duì)變量a的讀指令之前都會(huì)插入讀屏障。

a++;
//寫(xiě)屏障
//讀屏障
int b = a;

寫(xiě)屏障會(huì)保證寫(xiě)屏障之前的所有對(duì)共享數(shù)據(jù)的改動(dòng)都會(huì)同步到主存中。讀屏障會(huì)保證讀屏障之后對(duì)共享數(shù)據(jù)的讀取操作都會(huì)到主存去讀取。這樣就保證了,每次對(duì)valatile變量的修改對(duì)其他線程始終是可見(jiàn)的,從而保證了可見(jiàn)性。

另外,寫(xiě)屏障會(huì)保證寫(xiě)屏障之前的指令不會(huì)被排到寫(xiě)屏障后面。讀屏障會(huì)保證讀屏障之后的代碼不會(huì)排到讀屏障前面。這樣就保證了有序性。

如下,由于寫(xiě)屏障的存在,int b=1;語(yǔ)句只能排在 a++前面,不能顛倒順序。

int b=1;
a++;
//寫(xiě)屏障

volatile與加鎖的區(qū)別

volatile只能保證可見(jiàn)性和有序性,不能保證原子性,加鎖既可以保證可見(jiàn)性 原子性 有序性都可以保證。

volatile只適用于一個(gè)線程寫(xiě),多個(gè)線程讀的情況,對(duì)于多個(gè)線程寫(xiě)的情況,必須要加鎖。

加鎖相對(duì)于volatile是更加重量級(jí)的操作,所以一般能用volatile解決的問(wèn)題就不要加鎖。

先行發(fā)生原則

先行發(fā)生是Java內(nèi)存模型中定義的兩項(xiàng)操作之間的偏序關(guān)系。如果說(shuō)操作A先行發(fā)生于操作B,其實(shí)就是說(shuō)在發(fā)生操作B之前,操作A產(chǎn)生的影響被操作B察覺(jué)。

先行發(fā)生原則–是判斷是否存在數(shù)據(jù)競(jìng)爭(zhēng)、線程是否安全的主要依據(jù)。先行發(fā)生原則主要用來(lái)解決可見(jiàn)性問(wèn)題的。

如下代碼

//以下操作在線程A中執(zhí)行
i = 1;
//以下操作在線程B中執(zhí)行
j = i;
//以下操作在線程C中執(zhí)行
i = 2

如果A先行發(fā)生于B,B先行發(fā)生于C,那么必然j的值為1。如果A先行發(fā)生于B,B和C沒(méi)有先行發(fā)生關(guān)系,那么j的值可能為1也可能為2。

Java內(nèi)存模型存在一些天然的先行發(fā)生關(guān)系,這些先行發(fā)生關(guān)系不需要任何的同步操作,就可以保證其線程安全。

1、程序次序規(guī)則。在一個(gè)線程內(nèi),書(shū)寫(xiě)在前面的代碼先行發(fā)生于后面的。確切地說(shuō)應(yīng)該是,按照程序的控制流順序,因?yàn)榇嬖谝恍┓种ЫY(jié)構(gòu)。

2、Volatile變量規(guī)則。對(duì)一個(gè)volatile修飾的變量,對(duì)他的寫(xiě)操作先行發(fā)生于讀操作。

3、線程啟動(dòng)規(guī)則。Thread對(duì)象的start()方法先行發(fā)生于此線程的每一個(gè)動(dòng)作。

4、線程終止規(guī)則。線程的所有操作都先行發(fā)生于對(duì)此線程的終止檢測(cè)。

5、線程中斷規(guī)則。對(duì)線程interrupt()方法的調(diào)用先行發(fā)生于被中斷線程的代碼所檢測(cè)到的中斷事件。

6、對(duì)象終止規(guī)則。一個(gè)對(duì)象的初始化完成(構(gòu)造函數(shù)之行結(jié)束)先行發(fā)生于發(fā)的finilize()方法的開(kāi)始。

7、傳遞性。A先行發(fā)生B,B先行發(fā)生C,那么,A先行發(fā)生C。

8、管程鎖定規(guī)則。一個(gè)unlock操作先行發(fā)生于后面對(duì)同一個(gè)鎖的lock操作。

線程的三種實(shí)現(xiàn)方式

  • 使用內(nèi)核線程實(shí)現(xiàn)
  • 內(nèi)核線程就是直接由操作系統(tǒng)內(nèi)核支持的線程,通過(guò)內(nèi)核完成線程的切換。
  • 通過(guò)線程調(diào)度器來(lái)負(fù)責(zé)線程調(diào)度,即將線程任務(wù)分配到指定處理器。
  • 在用戶(hù)態(tài),每個(gè)內(nèi)核級(jí)線程會(huì)一 一對(duì)應(yīng)一個(gè)輕量級(jí)進(jìn)程,就是通常所說(shuō)的用戶(hù)級(jí)線程,多個(gè)用戶(hù)級(jí)線程可以組成一個(gè)用戶(hù)進(jìn)程。
  • 如下所示:p進(jìn)程 LWP用戶(hù)線程 KLT內(nèi)核線程 Thread Scheduler 線程調(diào)度器

在這里插入圖片描述

  • 由于內(nèi)核線程的支持,每個(gè)用戶(hù)線程都是獨(dú)立調(diào)度單位,即使有一個(gè)用戶(hù)線程阻塞了,也不會(huì)影響當(dāng)前進(jìn)程其他線程執(zhí)行。但是用戶(hù)線程切換 創(chuàng)建 終止都要內(nèi)核支持,內(nèi)核與用戶(hù)態(tài)切換代價(jià)較高。
  • Java就是使用內(nèi)核線程實(shí)現(xiàn)的,無(wú)論是windows還是linux都是基于內(nèi)核線程實(shí)現(xiàn)的。
  • 使用用戶(hù)線程實(shí)現(xiàn)
  • 操作系統(tǒng)內(nèi)核只能感知到用戶(hù)進(jìn)程,用戶(hù)進(jìn)程為操作系統(tǒng)內(nèi)核的基本調(diào)度單位。
  • 基于用戶(hù)進(jìn)程實(shí)現(xiàn)的用戶(hù)線程,線程的創(chuàng)建 切換 銷(xiāo)毀都是進(jìn)程自己管理,與內(nèi)核沒(méi)有關(guān)系。因?yàn)椴僮飨到y(tǒng)只能把處理器資源分配到進(jìn)程,那么線程的運(yùn)行 阻塞 生命周期管理都要用戶(hù)進(jìn)程自己來(lái)實(shí)現(xiàn)。
  • 內(nèi)核不參與線程調(diào)度,因此線程的上下文切換開(kāi)銷(xiāo)比較小,但是實(shí)現(xiàn)起來(lái)非常復(fù)雜,而且當(dāng)一個(gè)用戶(hù)級(jí)線程阻塞整個(gè)進(jìn)程都會(huì)阻塞,并發(fā)度不高。

在這里插入圖片描述

  • 混合模式實(shí)現(xiàn)
  • 用戶(hù)線程和內(nèi)核線程使用M對(duì)N的映射來(lái)實(shí)現(xiàn),兼顧兩者的優(yōu)點(diǎn)。

在這里插入圖片描述

總結(jié)

本篇文章就到這里了,希望能給你帶來(lái)幫助,也希望您能夠多多關(guān)注腳本之家的更多內(nèi)容!

相關(guān)文章

  • SpringBoot Import及自定義裝配實(shí)現(xiàn)方法解析

    SpringBoot Import及自定義裝配實(shí)現(xiàn)方法解析

    這篇文章主要介紹了SpringBoot Import及自定義裝配實(shí)現(xiàn)方法解析,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下
    2020-08-08
  • Java實(shí)現(xiàn)圖片驗(yàn)證碼具體代碼

    Java實(shí)現(xiàn)圖片驗(yàn)證碼具體代碼

    這篇文章主要為大家詳細(xì)介紹了Java實(shí)現(xiàn)圖片驗(yàn)證碼具體代碼,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下
    2016-10-10
  • 查看java對(duì)象所占內(nèi)存大小的方法

    查看java對(duì)象所占內(nèi)存大小的方法

    這篇文章主要為大家介紹了如何查看java對(duì)象所占內(nèi)存大小的方法,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪
    2023-09-09
  • Java基礎(chǔ)教程之final關(guān)鍵字淺析

    Java基礎(chǔ)教程之final關(guān)鍵字淺析

    這篇文章主要給大家介紹了關(guān)于Java基礎(chǔ)教程之final關(guān)鍵字的相關(guān)資料,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面來(lái)一起學(xué)習(xí)學(xué)習(xí)吧
    2019-06-06
  • Java垃圾回收機(jī)制簡(jiǎn)述

    Java垃圾回收機(jī)制簡(jiǎn)述

    這篇文章主要為大家詳細(xì)介紹了Java垃圾回收機(jī)制的相關(guān)資料,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下
    2018-01-01
  • SpringBoot集成Druid連接池進(jìn)行SQL監(jiān)控的問(wèn)題解析

    SpringBoot集成Druid連接池進(jìn)行SQL監(jiān)控的問(wèn)題解析

    這篇文章主要介紹了SpringBoot集成Druid連接池進(jìn)行SQL監(jiān)控的問(wèn)題解析,在SpringBoot工程中引入Druid連接池非常簡(jiǎn)單,本文通過(guò)實(shí)例代碼給大家介紹的非常詳細(xì),需要的朋友可以參考下
    2021-07-07
  • 如何基于SpringWeb?MultipartFile實(shí)現(xiàn)文件上傳、下載功能

    如何基于SpringWeb?MultipartFile實(shí)現(xiàn)文件上傳、下載功能

    在做項(xiàng)目時(shí),后端經(jīng)常采用上傳文件組件MultipartFile,下面這篇文章主要給大家介紹了關(guān)于如何基于SpringWeb?MultipartFile實(shí)現(xiàn)文件上傳、下載功能的相關(guān)資料,文中通過(guò)代碼介紹的非常詳細(xì),需要的朋友可以參考下
    2024-07-07
  • Spring Boot 2.x 把 Guava 干掉了選擇本地緩存之王 Caffeine(推薦)

    Spring Boot 2.x 把 Guava 干掉了選擇本地緩存之王 Caffeine(推薦)

    這篇文章主要介紹了Spring Boot 2.x 把 Guava 干掉了選擇本地緩存之王 Caffeine,本文給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下
    2021-01-01
  • Spring Boot MyBatis 連接數(shù)據(jù)庫(kù)配置示例

    Spring Boot MyBatis 連接數(shù)據(jù)庫(kù)配置示例

    本篇文章主要介紹了Spring Boot MyBatis 連接數(shù)據(jù)庫(kù)示例的相關(guān)資料,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下。
    2017-02-02
  • SpringBoot+Mybatis+Vue 實(shí)現(xiàn)商品模塊的crud操作

    SpringBoot+Mybatis+Vue 實(shí)現(xiàn)商品模塊的crud操作

    這篇文章主要介紹了SpringBoot+Mybatis+Vue 實(shí)現(xiàn)商品模塊的crud操作,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧
    2020-10-10

最新評(píng)論