最新国产好看的视频,伊人天堂AV在线,国产Aaaaaa视频,蜜臀视频在线观看一区,人妻av色图,密臀久久久精品影片,青青视频免费观看毛片,久草在线观看视,国产三级精品色情在线

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

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

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)境下原子性,可見性,有序性的規(guī)則和保障。
  • Java內(nèi)存模型提供了主內(nèi)存和工作內(nèi)存兩種抽象,主內(nèi)存指的是共享區(qū)域 ,工作內(nèi)存指的是線程私有工作空間。
  • 當(dāng)一個線程訪問共享數(shù)據(jù)時,需要先將共享數(shù)據(jù)復(fù)制一份副本到線程的工作內(nèi)存(類比操作系統(tǒng)中的高速緩存),然后在工作內(nèi)存進行操作,最后再把工作內(nèi)存數(shù)據(jù)覆蓋到主內(nèi)存。主內(nèi)存和工作內(nèi)存交互通過特定指令完成。
  • 如下為并發(fā)內(nèi)存模型圖

在這里插入圖片描述

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

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

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

  • lock:把主內(nèi)存的一個變量標(biāo)記為一個線程鎖定狀態(tài)。
  • unlock:把主內(nèi)存中處于鎖定狀態(tà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)存的值寫入到工作內(nèi)存變量。

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

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

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

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

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

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

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

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

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

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來禁用指令重排達到有序性。

加鎖也可以避免指令重排帶來的混亂,但是本身并沒有禁止指令重排,因為保證了原子性,所以即使指令重排在同步代碼塊中依然相當(dāng)于單線程執(zhí)行,也不會有邏輯上的錯誤。

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

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

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

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

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

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

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

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

valatile原理

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

valatile int a = 0;

那么,對變量a的寫指令之后都會插入寫屏障,對變量a的讀指令之前都會插入讀屏障。

a++;
//寫屏障
//讀屏障
int b = a;

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

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

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

int b=1;
a++;
//寫屏障

volatile與加鎖的區(qū)別

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

volatile只適用于一個線程寫,多個線程讀的情況,對于多個線程寫的情況,必須要加鎖。

加鎖相對于volatile是更加重量級的操作,所以一般能用volatile解決的問題就不要加鎖。

先行發(fā)生原則

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

先行發(fā)生原則–是判斷是否存在數(shù)據(jù)競爭、線程是否安全的主要依據(jù)。先行發(fā)生原則主要用來解決可見性問題的。

如下代碼

//以下操作在線程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沒有先行發(fā)生關(guān)系,那么j的值可能為1也可能為2。

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

1、程序次序規(guī)則。在一個線程內(nèi),書寫在前面的代碼先行發(fā)生于后面的。確切地說應(yīng)該是,按照程序的控制流順序,因為存在一些分支結(jié)構(gòu)。

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

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

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

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

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

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

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

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

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

在這里插入圖片描述

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

在這里插入圖片描述

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

在這里插入圖片描述

總結(jié)

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

相關(guān)文章

  • SpringBoot使用攔截器Interceptor實現(xiàn)統(tǒng)一角色權(quán)限校驗

    SpringBoot使用攔截器Interceptor實現(xiàn)統(tǒng)一角色權(quán)限校驗

    角色權(quán)限校驗,是保證接口安全必備的能力:有權(quán)限才可以操作,所以,一般對于這種通用邏輯,推薦不與主業(yè)務(wù)邏輯耦合,那么怎么來解耦,那么本文小編就給大家詳細講解如何使用攔截器Interceptor實現(xiàn)統(tǒng)一角色權(quán)限校驗,需要的朋友可以參考下
    2023-07-07
  • Java實現(xiàn)用位運算維護狀態(tài)碼

    Java實現(xiàn)用位運算維護狀態(tài)碼

    位運算是一種非常高效的運算方式,在算法考察中比較常見,那么業(yè)務(wù)代碼中我們?nèi)绾问褂梦贿\算呢,感興趣的小伙伴快跟隨小編一起學(xué)習(xí)一下吧
    2024-03-03
  • Swing中依據(jù)鼠標(biāo)拖拽來畫出矩形的實現(xiàn)方法

    Swing中依據(jù)鼠標(biāo)拖拽來畫出矩形的實現(xiàn)方法

    這篇文章主要介紹了Swing中依據(jù)鼠標(biāo)拖拽來畫出矩形的實現(xiàn)方法,文中通過示例代碼介紹的非常詳細,對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧
    2019-11-11
  • Java 中圖片壓縮處理的解決方案

    Java 中圖片壓縮處理的解決方案

    圖片經(jīng)過base64編碼轉(zhuǎn)換后,文件會變大的原因是因為base64編碼會將每個3字節(jié)的數(shù)據(jù)轉(zhuǎn)換成4字節(jié)的數(shù)據(jù),并且在轉(zhuǎn)換的過程中還會添加一些額外的字符,這篇文章主要介紹了Java 中如何對圖片進行壓縮處理,需要的朋友可以參考下
    2023-09-09
  • Java如何發(fā)起http請求的實現(xiàn)(GET/POST)

    Java如何發(fā)起http請求的實現(xiàn)(GET/POST)

    這篇文章主要介紹了Java如何發(fā)起http請求的實現(xiàn),文中通過示例代碼介紹的非常詳細,對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧
    2021-03-03
  • maven如何動態(tài)統(tǒng)一修改版本號的方法步驟

    maven如何動態(tài)統(tǒng)一修改版本號的方法步驟

    這篇文章主要介紹了maven如何動態(tài)統(tǒng)一修改版本號的方法步驟,文中通過示例代碼介紹的非常詳細,對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧
    2020-12-12
  • springboot如何通過controller層實現(xiàn)頁面切換

    springboot如何通過controller層實現(xiàn)頁面切換

    在Spring Boot中,通過Controller層實現(xiàn)頁面切換背景,Spring Boot的默認注解是@RestController,它包含了@Controller和@ResponseBody,@ResponseBody會將返回值轉(zhuǎn)換為字符串返回,因此無法實現(xiàn)頁面切換,將@RestController換成@Controller
    2024-12-12
  • java實現(xiàn)簡單學(xué)生管理系統(tǒng)項目

    java實現(xiàn)簡單學(xué)生管理系統(tǒng)項目

    這篇文章主要介紹了java實現(xiàn)簡單學(xué)生管理系統(tǒng)項目,文中示例代碼介紹的非常詳細,具有一定的參考價值,感興趣的小伙伴們可以參考一下
    2020-07-07
  • RabbitMQ中的channel信道、exchange交換機和queue隊列詳解

    RabbitMQ中的channel信道、exchange交換機和queue隊列詳解

    這篇文章主要介紹了RabbitMQ中的channel信道、exchange交換機和queue隊列詳解,connection是指物理的連接,一個client與一個server之間有一個連接,一個連接上可以建立多個channel,可以理解為邏輯上的連接,需要的朋友可以參考下
    2023-08-08
  • java多線程之火車售票系統(tǒng)模擬實例

    java多線程之火車售票系統(tǒng)模擬實例

    下面小編就為大家?guī)硪黄猨ava多線程之火車售票系統(tǒng)模擬實例。小編覺得挺不錯的,現(xiàn)在就分享給大家,也給大家做個參考。一起跟隨小編過來看看吧
    2017-06-06

最新評論

江油市| 乐清市| 伽师县| 东辽县| 佛坪县| 新巴尔虎左旗| 耒阳市| 娄底市| 屏东市| 台北县| 元朗区| 彭山县| 华阴市| 香格里拉县| 金平| 内江市| 喀什市| 大理市| 星子县| 天柱县| 蛟河市| 镇坪县| 罗定市| 天峨县| 锦州市| 中江县| 裕民县| 清丰县| 双流县| 安平县| 平湖市| 张家港市| 凤阳县| 汉川市| 增城市| 河曲县| 大安市| 九江市| 托克托县| 南溪县| 南木林县|