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

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ā)生線程切換的,或者說是可以被其他線程中斷的。所以,++操作不是原子操作,在并行場(chǎng)景會(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)存可見性問題,所有線程都必須將共享變量刷新到主存,一種簡(jiǎn)單的方案是:使用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 spring 中類注冊(cè)六種方式

    詳解Java spring 中類注冊(cè)六種方式

    在Spring中,將類注冊(cè)為容器管理的Bean有多種方式,涵蓋注解驅(qū)動(dòng)、配置類、XML、動(dòng)態(tài)注冊(cè)等場(chǎng)景,本文給大家介紹的非常詳細(xì),感興趣的朋友跟隨小編一起看看吧
    2025-09-09
  • Java替換字符串replace和replaceAll方法舉例詳解

    Java替換字符串replace和replaceAll方法舉例詳解

    這篇文章主要介紹了Java中替換字符串的幾種方法,包括String類的replace()、replaceAll()、replaceFirst()方法,以及StringBuilder和StringBuffer類的replace()方法,還提到了一些第三方庫,如Hutool,它們提供了更豐富的字符串處理功能,需要的朋友可以參考下
    2025-02-02
  • Java中的接口多繼承機(jī)制

    Java中的接口多繼承機(jī)制

    大家好,本篇文章主要講的是Java中的接口多繼承機(jī)制,感興趣的同學(xué)趕快來看一看吧,對(duì)你有幫助的話記得收藏一下
    2022-02-02
  • java隊(duì)列之queue用法實(shí)例分析

    java隊(duì)列之queue用法實(shí)例分析

    這篇文章主要介紹了java隊(duì)列之queue用法實(shí)例分析,Queue 隊(duì)列就是一個(gè)先入先出(FIFO)的數(shù)據(jù)結(jié)構(gòu),Queue接口繼承Collection接口。感興趣的可以了解一下
    2020-07-07
  • Java Socket編程實(shí)現(xiàn)群聊實(shí)踐案例

    Java Socket編程實(shí)現(xiàn)群聊實(shí)踐案例

    本文介紹了如何實(shí)現(xiàn)特定客戶端與特定客戶端之間的私聊和群聊功能,通過服務(wù)器端的多線程處理和客戶端的Socket連接,實(shí)現(xiàn)了消息的路由和轉(zhuǎn)發(fā),本文給大家介紹Java Socket編程實(shí)現(xiàn)群聊功能,感興趣的朋友跟隨小編一起看看吧
    2025-12-12
  • 后端報(bào)TypeError:Cannot?read?properties?of?null?(reading?‘xxx‘)的錯(cuò)誤解決

    后端報(bào)TypeError:Cannot?read?properties?of?null?(reading?‘

    這篇文章主要給大家介紹了關(guān)于后端報(bào)TypeError:Cannot?read?properties?of?null?(reading?‘xxx‘)錯(cuò)誤的解決辦法,這個(gè)錯(cuò)誤是開發(fā)中常見的錯(cuò)誤之一,需要的朋友可以參考下
    2023-05-05
  • SpringBoot集成內(nèi)存數(shù)據(jù)庫H2的實(shí)踐

    SpringBoot集成內(nèi)存數(shù)據(jù)庫H2的實(shí)踐

    h2是內(nèi)存數(shù)據(jù)庫,查詢高效,可以在開發(fā)初期使用它。本文主要介紹了SpringBoot集成內(nèi)存數(shù)據(jù)庫H2的實(shí)踐,具有一定的參考價(jià)值,感興趣的可以了解一下
    2021-09-09
  • 基于Mybatis-Plus攔截器實(shí)現(xiàn)MySQL數(shù)據(jù)加解密的示例代碼

    基于Mybatis-Plus攔截器實(shí)現(xiàn)MySQL數(shù)據(jù)加解密的示例代碼

    用戶的一些敏感數(shù)據(jù),例如手機(jī)號(hào)、郵箱、身份證等信息,在數(shù)據(jù)庫以明文存儲(chǔ)時(shí)會(huì)存在數(shù)據(jù)泄露的風(fēng)險(xiǎn),因此需要進(jìn)行加密,解密等功能,接下來本文就給大家介紹基于Mybatis-Plus攔截器實(shí)現(xiàn)MySQL數(shù)據(jù)加解密,需要的朋友可以參考下
    2023-07-07
  • Java調(diào)用Windows的DOS命令的方法

    Java調(diào)用Windows的DOS命令的方法

    這篇文章主要介紹了Java調(diào)用Windows的DOS命令的方法,其主要內(nèi)容思路是實(shí)現(xiàn)調(diào)用Windows的ipconfig命令,然后將輸出的信息通過IO流輸出到控制臺(tái)。有需要的小伙伴參考下
    2015-02-02
  • Java開發(fā)Spark應(yīng)用程序自定義PipeLineStage詳解

    Java開發(fā)Spark應(yīng)用程序自定義PipeLineStage詳解

    這篇文章主要為大家介紹了Java開發(fā)Spark應(yīng)用程序自定義PipeLineStage詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪
    2023-02-02

最新評(píng)論

芷江| 交城县| 彩票| 开原市| 大理市| 腾冲县| 葵青区| 松潘县| 四川省| 前郭尔| 麻城市| 和林格尔县| 简阳市| 正定县| 梁平县| 靖宇县| 大石桥市| 广安市| 获嘉县| 聂荣县| 永吉县| 绥芬河市| 泊头市| 阿城市| 夹江县| 台北市| 奇台县| 太仆寺旗| 榆树市| 河西区| 肃北| 扎兰屯市| 阳春市| 宾阳县| 开平市| 敖汉旗| 丹江口市| 新余市| 凉城县| 安新县| 砚山县|