亚洲乱码中文字幕综合,中国熟女仑乱hd,亚洲精品乱拍国产一区二区三区,一本大道卡一卡二卡三乱码全集资源,又粗又黄又硬又爽的免费视频

Java多線程的原子性,可見性,有序性你都了解嗎

 更新時(shí)間:2022年03月02日 09:16:21   作者:小小茶花女  
這篇文章主要為大家詳細(xì)介紹了Java多線程的原子性,可見性,有序性,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下,希望能夠給你帶來幫助

問題:

1.什么是原子性、可見性、有序性?

1. 原子性問題

原子性、可見性、有序性是并發(fā)編程所面臨的三大問題。

所謂原子操作,就是“不可中斷的一個(gè)或一系列操作”,是指不會(huì)被線程調(diào)度機(jī)制打斷的操作。這種操作一旦開始,就一直運(yùn)行到結(jié)束,中間不會(huì)有任何線程的切換。

例如對(duì)于 i++ 而言,實(shí)際會(huì)產(chǎn)生如下的 JVM 字節(jié)碼指令:

getstatic i  // 獲取靜態(tài)變量i的值(內(nèi)存取值)
iconst_1     // 準(zhǔn)備常量1
iadd         // 自增 (寄存器增加1)
putstatic i  // 將修改后的值存入靜態(tài)變量i(存值到內(nèi)存)

如果是單線程以上 8 行代碼是順序執(zhí)行(不會(huì)交錯(cuò))沒有問題:

在這里插入圖片描述

但多線程下這 8 行代碼可能交錯(cuò)運(yùn)行:

出現(xiàn)負(fù)數(shù)的情況:

在這里插入圖片描述

出現(xiàn)正數(shù)的情況:

在這里插入圖片描述

一個(gè)自增運(yùn)算符是一個(gè)復(fù)合操作,“內(nèi)存取值”“寄存器增加1”和“存值到內(nèi)存”這三個(gè)JVM指令本身是不可再分的,它們都具備原子性,是線程安全的,也叫原子操作。但是,兩個(gè)或者兩個(gè)以上的原子操作合在一起進(jìn)行操作就不再具備原子性了。比如先讀后寫,就有可能在讀之后,其實(shí)這個(gè)變量被修改了,出現(xiàn)讀和寫數(shù)據(jù)不一致的情況。

因?yàn)檫@4個(gè)操作之間是可以發(fā)生線程切換的,或者說是可以被其他線程中斷的。所以,++操作不是原子操作,在并行場景會(huì)發(fā)生原子性問題。

2. 可見性問題

一個(gè)線程對(duì)共享變量的修改,另一個(gè)線程能夠立刻可見,我們稱該共享變量具備內(nèi)存可見性。

談到內(nèi)存可見性,要先引出Java內(nèi)存模型的概念。JMM規(guī)定,將所有的變量都存放在公共主存中,當(dāng)線程使用變量時(shí)會(huì)把主存中的變量復(fù)制到自己的工作內(nèi)存(私有內(nèi)存)中,線程對(duì)變量的讀寫操作,是自己工作內(nèi)存中的變量副本。

如果兩個(gè)線程同時(shí)操作一個(gè)共享變量,就可能發(fā)生可見性問題:

(1) 主存中有變量sum,初始值sum=0;

(2) 線程A計(jì)劃將sum加1,先將sum=0復(fù)制到自己的私有內(nèi)存中,然后更新sum的值,線程A操作完成之后其私有內(nèi)存中sum=1,然而線程A將更新后的sum值回刷到主存的時(shí)間是不固定的;

(3) 在線程A沒有回刷sum到主存前,剛好線程B同樣從主存中讀取sum,此時(shí)值為0,和線程A進(jìn)行同樣的操作,最后期盼的sum=2目標(biāo)沒有達(dá)成,最終sum=1;

線程A和線程B并發(fā)操作sum發(fā)生內(nèi)存可見性問題:

在這里插入圖片描述

要想解決多線程的內(nèi)存可見性問題,所有線程都必須將共享變量刷新到主存,一種簡單的方案是:使用Java提供的關(guān)鍵字volatile修飾共享變量。

為什么Java局部變量、方法參數(shù)不存在內(nèi)存可見性問題?

在Java中,所有的局部變量、方法定義參數(shù)都不會(huì)在線程之間共享,所以也就不會(huì)有內(nèi)存可見性問題。所有的Object實(shí)例、Class實(shí)例和數(shù)組元素都存儲(chǔ)在JVM堆內(nèi)存中,堆內(nèi)存在線程之間共享,所以存在可見性問題。

3. 有序性問題

所謂程序的有序性,是指程序按照代碼的先后順序執(zhí)行。如果程序執(zhí)行的順序與代碼的先后順序不同,并導(dǎo)致了錯(cuò)誤的結(jié)果,即發(fā)生了有序性問題。

@Slf4j
public class Test3 {
    private static volatile int x=0,y=0;
    private static int a=0,b=0;

    public static void main(String[] args) throws InterruptedException {
        for(int i=0;;i++){
            a=0;
            b=0;
            x=0;
            y=0;
            Thread t1 = new Thread(() -> {
                a = 1;
                x = b;
            });

            Thread t2 = new Thread(() -> {
                b = 1;
                y = a;
            });
            t1.start();
            t2.start();
            t1.join();
            t2.join();
            // 假如t1線程先執(zhí)行,t2線程后執(zhí)行,則結(jié)果為a=1,x=0,b=1,y=1  (0,1)
            // 假如t2線程先執(zhí)行,t1線程后執(zhí)行,則結(jié)果為b=1,y=0,a=1,x=1  (1,0)
            // 假如t1線程和t2線程的指令是同時(shí)或交替執(zhí)行的,則結(jié)果為a=1,b=1,x=1,y=1 (1,1)
            // 但是不可能出現(xiàn)(0,0)
            if(x==0 && y==0){
                log.debug("x:{}, y:{}",x,y);
            }
        }
    }
}

由于并發(fā)執(zhí)行的無序性,賦值之后x、y的值可能為(1,0)、(0,1)或(1,1)。為什么呢?因?yàn)榫€程t1可能在線程t2開始之前就執(zhí)行完了,也可能線程t2在線程t1開始之前就執(zhí)行完了,甚至有可能二者的指令是同時(shí)或交替執(zhí)行的。

然而,執(zhí)行以上代碼時(shí),出乎意料的事情發(fā)生了:這段代碼的執(zhí)行結(jié)果也可能是(0,0),部分結(jié)果如下:

19:37:32.113 [main] DEBUG com.example.test.Test3 - x:0, y:0
19:37:33.041 [main] DEBUG com.example.test.Test3 - x:0, y:0
19:37:34.501 [main] DEBUG com.example.test.Test3 - x:0, y:0
19:37:41.825 [main] DEBUG com.example.test.Test3 - x:0, y:0

于以上程序來說,(0,0)結(jié)果是錯(cuò)誤的,意味著已經(jīng)發(fā)生了并發(fā)的有序性問題。為什么會(huì)出現(xiàn)(0,0)結(jié)果呢?可能在程序的執(zhí)行過程中發(fā)生了指令重排序。對(duì)于線程t1來說,可能a=1和x=b這兩個(gè)語句的賦值操作順序被顛倒了,對(duì)于線程t2來說,可能b=1和y=a這兩個(gè)語句的賦值操作順序被顛倒了,從而出現(xiàn)了(x,y)值為(0,0)的錯(cuò)誤結(jié)果。

什么是指令重排序?

一般來說,CPU為了提高程序運(yùn)行效率,可能會(huì)對(duì)輸入代碼進(jìn)行優(yōu)化,它不保證程序中各個(gè)語句的執(zhí)行順序同代碼中的先后順序一致,但是它會(huì)保證程序最終的執(zhí)行結(jié)果和代碼順序執(zhí)行的結(jié)果是一致的。

重排序也是單核時(shí)代非常優(yōu)秀的優(yōu)化手段,有足夠多的措施保證其在單核下的正確性。在多核時(shí)代,如果工作線程之間不共享數(shù)據(jù)或僅共享不可變數(shù)據(jù),重排序也是性能優(yōu)化的利器。然而,如果工作線程之間共享了可變數(shù)據(jù),由于兩種重排序的結(jié)果都不是固定的,因此會(huì)導(dǎo)致工作線程似乎表現(xiàn)出了隨機(jī)行為。指令重排序不會(huì)影響單個(gè)線程的執(zhí)行,但是會(huì)影響多個(gè)線程并發(fā)執(zhí)行的正確性。

事實(shí)上,輸出了亂序的結(jié)果,并不代表一定發(fā)生了指令重排序,內(nèi)存可見性問題也會(huì)導(dǎo)致這樣的輸出。但是,指令重排序也是導(dǎo)致亂序的原因之一。

總之,要想并發(fā)程序正確地執(zhí)行,必須要保證原子性、可見性以及有序性。只要有一個(gè)沒有得到保證,就有可能會(huì)導(dǎo)致程序運(yùn)行不正確。

總結(jié)

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

相關(guān)文章

  • 如何去除Java中List集合中的重復(fù)數(shù)據(jù)

    如何去除Java中List集合中的重復(fù)數(shù)據(jù)

    這篇文章主要介紹了Java中List集合去除重復(fù)數(shù)據(jù)的方法,對(duì)大家的工作或?qū)W習(xí)有一定價(jià)值,有需求的朋友可以參考下
    2020-05-05
  • Java面向?qū)ο蠡A(chǔ)知識(shí)之?dāng)?shù)組和鏈表

    Java面向?qū)ο蠡A(chǔ)知識(shí)之?dāng)?shù)組和鏈表

    這篇文章主要介紹了Java面向?qū)ο蟮闹當(dāng)?shù)組和鏈表,文中有非常詳細(xì)的代碼示例,對(duì)正在學(xué)習(xí)java基礎(chǔ)的小伙伴們有很好的幫助,需要的朋友可以參考下
    2021-11-11
  • SpringBoot實(shí)現(xiàn)定時(shí)任務(wù)動(dòng)態(tài)管理示例

    SpringBoot實(shí)現(xiàn)定時(shí)任務(wù)動(dòng)態(tài)管理示例

    這篇文章主要為大家介紹了SpringBoot實(shí)現(xiàn)定時(shí)任務(wù)動(dòng)態(tài)管理示例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪
    2023-06-06
  • Spring報(bào)錯(cuò):Error creating bean with name的問題及解決

    Spring報(bào)錯(cuò):Error creating bean with name的問

    這篇文章主要介紹了Spring報(bào)錯(cuò):Error creating bean with name的問題及解決方案,具有很好的參考價(jià)值,希望對(duì)大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教
    2023-08-08
  • Java動(dòng)態(tài)代理的示例詳解

    Java動(dòng)態(tài)代理的示例詳解

    動(dòng)態(tài)代理指的是,代理類和目標(biāo)類的關(guān)系在程序運(yùn)行的時(shí)候確定的,客戶通過代理類來調(diào)用目標(biāo)對(duì)象的方法,是在程序運(yùn)行時(shí)根據(jù)需要?jiǎng)討B(tài)的創(chuàng)建目標(biāo)類的代理對(duì)象。本文將通過案例詳細(xì)講解一下動(dòng)態(tài)代理,需要的可以參考一下
    2022-02-02
  • MyBatis中ResultMap與多表查詢的處理方法

    MyBatis中ResultMap與多表查詢的處理方法

    這篇文章主要介紹了MyBatis中ResultMap與多表查詢的處理方法,本文通過實(shí)例代碼給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下
    2023-09-09
  • JDBC核心技術(shù)詳解

    JDBC核心技術(shù)詳解

    這篇文章主要介紹了JDBC核心技術(shù)詳解,文中有非常詳細(xì)的代碼示例,對(duì)正在學(xué)習(xí)JDBC的小伙伴們有很好的幫助,需要的朋友可以參考下
    2021-05-05
  • spring框架下@value注解屬性static無法獲取值問題

    spring框架下@value注解屬性static無法獲取值問題

    這篇文章主要介紹了spring框架下@value注解屬性static無法獲取值問題,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教
    2021-11-11
  • Java圖文分析之繼承內(nèi)存布局

    Java圖文分析之繼承內(nèi)存布局

    這篇文章主要介紹了Java圖文分析之繼承內(nèi)存布局,文章圍繞主題展開詳細(xì)的內(nèi)容介紹,具有一定的參考價(jià)值,感興趣的朋友可以參考一下
    2022-09-09
  • Maven打包并生成運(yùn)行腳本的示例代碼

    Maven打包并生成運(yùn)行腳本的示例代碼

    這篇文章主要介紹了Maven打包并生成運(yùn)行腳本,本文通過實(shí)例代碼給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下
    2020-07-07

最新評(píng)論