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

Java 并發(fā)編程的可見性、有序性和原子性

 更新時間:2020年11月17日 08:50:46   作者:H.U.C-王子  
這篇文章主要介紹了Java 并發(fā)編程的可見性、有序性和原子性的相關(guān)資料,幫助大家更好的理解和學(xué)習(xí)Java并發(fā)編程,感興趣的朋友可以了解下。

并發(fā)編程無論在哪門語言里,都屬于高級篇,面試中也嘗嘗會被問到。想要深入理解并發(fā)編程機(jī)制確實不是一件容易的事,因為它涉及到計算機(jī)底層和操作系統(tǒng)的相關(guān)知識,如果對這部分知識不是很清楚可能會導(dǎo)致理解困難。

在這個專欄里,王子會盡量以白話和圖片的方式剖析并發(fā)編程本質(zhì),希望可以讓大家更容易理解。

今天我們就來談一談可見性、有序性和原子性都是什么東西。

并發(fā)編程的幕后

進(jìn)入主題之前,我們先來了解一下并發(fā)編程的幕后。

隨著CPU、內(nèi)存和I/O設(shè)備的不斷升級,它們之間一直存在著一個矛盾,就是速度不一致問題。CPU的速度高于內(nèi)存,內(nèi)存的速度又高于I/O設(shè)備。

我們寫的代碼中大多數(shù)內(nèi)容都會經(jīng)過內(nèi)存處理,有些內(nèi)容會去讀寫I/O設(shè)備,根據(jù)木桶理論,整體的性能取決于最慢的操作,就是I/O設(shè)備,所以單單提升CPU的性能是不夠的。

為了最大化體現(xiàn)出CPU的性能,計算機(jī)底層主要做了三部分優(yōu)化:

1.CPU增加了緩存,比內(nèi)存速度更快,平衡內(nèi)存的速度

2.操作系統(tǒng)增加了進(jìn)程和線程,可以對CPU分時復(fù)用

3.編譯程序會進(jìn)行指令的重排,使緩存更好的發(fā)揮性能

我們平時的工作中其實一直都享受著這些優(yōu)化后的成果,但同時他們也會導(dǎo)致一些很難找到原因的BUG。

什么是可見性

首先我們就來看看什么是可見性。

一個線程對共享變量的修改,另一個線程可以感知到,我們稱其為可見性。

在單核時代,其實是不存在可見性問題的,因為所有的線程都是在一個CPU中工作的,一個線程的寫操作對于其他的線程一定是可見的。

但是多核CPU出現(xiàn)后,每個CPU都有自己的緩存,多個線程在不同的CPU中處理數(shù)據(jù)就會導(dǎo)致不可見問題。

假設(shè)變量v的值是1, 兩個線程同時執(zhí)行了v++操作,首先會從內(nèi)存中讀取變量v的數(shù)據(jù)到各自的CPU緩存中,這個時候兩個CPU緩存中的v都是1,執(zhí)行v++后,兩個變量v都變成了2,然后再寫回內(nèi)存,內(nèi)存中的變量v就變成了2。

但其實我們想看到的結(jié)果v最終應(yīng)該是3才對。

在CPU1緩存中執(zhí)行v++后,CPU2緩存無法感知的到,這就是可見性問題。而由于可見性問題導(dǎo)致的最終數(shù)據(jù)不正確,就是線程安全問題。

什么是原子性

由于I/O的速度太慢,早期的操作系統(tǒng)發(fā)明了多進(jìn)程,就是允許某個進(jìn)程執(zhí)行一小段時間后,重新選擇一個進(jìn)程來執(zhí)行,這個過程叫做任務(wù)切換,而這一小段的時間我們稱其為時間片。

現(xiàn)在操作系統(tǒng)的任務(wù)切換一般指的是更輕量級的線程切換,java的并發(fā)編程是基于多線程的,自然也會存在線程切換。

一般會在時間片結(jié)束的時候進(jìn)行線程切換,java語言中執(zhí)行的一段簡單的代碼往往需要多條CPU的指令實現(xiàn),比如count++這部分代碼,至少需要三條CPU指令:

1.首先把count從內(nèi)存中讀取到CPU的寄存器中

2.在寄存器中執(zhí)行+1操作

3.最后將count的值寫入內(nèi)存中(可能寫入到CPU的緩存中)

而線程切換是可以發(fā)生在任意的一條CPU指令執(zhí)行之后的,注意,這里說的是CPU的指令,而不是java語言中的指令,對于上面的三條指令來說,我們假設(shè) count=0,如果線程 A 在指令 1 執(zhí)行完后做線程切換,線程 A 和線程 B 按照下圖的順序執(zhí)行,那么我們會發(fā)現(xiàn)兩個線程都執(zhí)行了 count++ 的操作,但是得到的結(jié)果不是我們期望的 2,而是 1。

這就是線程切換導(dǎo)致的數(shù)據(jù)錯誤問題,我們把一個或者多個操作在 CPU 執(zhí)行的過程中不被中斷的特性稱為原子性,CPU 能保證的原子操作是 CPU 指令級別的,而不是高級語言的操作符,這是違背我們直覺的地方。因此,很多時候我們需要在高級語言層面保證操作的原子性。

什么是有序性

有序性指的是程序按照代碼的先后順序執(zhí)行。編譯器為了優(yōu)化性能,有時候會改變程序中語句的先后順序,例如程序中:“x=1;y=2;”編譯器優(yōu)化后可能變成“y=2;x=1;”。

在這個例子中,編譯器調(diào)整了語句的順序,但是不影響程序的最終結(jié)果。不過有時候調(diào)整了語句的順序可能導(dǎo)致意想不到的 Bug。

在 Java 領(lǐng)域一個經(jīng)典的案例就是利用雙重檢查創(chuàng)建單例對象,代碼如下:

public class Singleton {
 static Singleton instance;
 static Singleton getInstance(){
  if (instance == null) {
   synchronized(Singleton.class) {
    if (instance == null)
     instance = new Singleton();
    }
  }
  return instance;
 }
}

假設(shè)有兩個線程 A、B 同時調(diào)用 getInstance() 方法,他們會同時發(fā)現(xiàn) instance == null ,于是同時對 Singleton.class 加鎖,此時 JVM 保證只有一個線程能夠加鎖成功(假設(shè)是線程 A),另外一個線程則會處于等待狀態(tài)(假設(shè)是線程 B);線程 A 會創(chuàng)建一個 Singleton 實例,之后釋放鎖,鎖釋放后,線程 B 被喚醒,線程 B 再次嘗試加鎖,此時是可以加鎖成功的,加鎖成功后,線程 B 檢查 instance == null 時會發(fā)現(xiàn),已經(jīng)創(chuàng)建過 Singleton 實例了,所以線程 B 不會再創(chuàng)建一個 Singleton 實例。

這個過程看上去是不是無懈可擊,沒有漏洞?

答案是否定的,問題就出在了new操作上,我們以為的new操作是這樣的:

1.分配一塊內(nèi)存空間

2.在這塊內(nèi)存空間上初始化Singleton實例對象

3.把這個對象的內(nèi)存地址賦值給instance變量

但實際上由于指令重排,優(yōu)化后的過程是這樣的:

1.分配一塊內(nèi)存空間

2.把這快內(nèi)存空間的內(nèi)存地址賦值給instance變量

3.在這塊內(nèi)存空間上初始化Singleton實例對象 

那么這樣調(diào)換順序后會發(fā)生什么呢?

我們假設(shè)線程 A 先執(zhí)行 getInstance() 方法,當(dāng)執(zhí)行完指令 2 時恰好發(fā)生了線程切換,切換到了線程 B 上;如果此時線程 B 也執(zhí)行 getInstance() 方法,那么線程 B 在執(zhí)行第一個判斷時會發(fā)現(xiàn) instance != null ,所以直接返回 instance,而此時的 instance 是沒有初始化過的,如果我們這個時候訪問 instance 的成員變量就可能觸發(fā)空指針異常。 

總結(jié)

使用并發(fā)編程開發(fā),往往會出現(xiàn)很多難以找到原因的BUG,通過對可見性、有序性和原子性的分析,可以為我們排查并發(fā)導(dǎo)致的BUG提供一些思路。

CPU緩存會導(dǎo)致可見性

指令重排會導(dǎo)致有序性

線程切換會導(dǎo)致原子性

以上就是本篇文章的三個核心內(nèi)容,那我們下篇文章繼續(xù)。

以上就是Java 并發(fā)編程的可見性、有序性和原子性的詳細(xì)內(nèi)容,更多關(guān)于Java 并發(fā)編程的資料請關(guān)注腳本之家其它相關(guān)文章!

相關(guān)文章

  • Springboot啟動報錯時實現(xiàn)異常定位

    Springboot啟動報錯時實現(xiàn)異常定位

    這篇文章主要介紹了Springboot啟動報錯時實現(xiàn)異常定位,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友可以參考下
    2020-06-06
  • idea中如何過濾某些文件不提交的方法實現(xiàn)

    idea中如何過濾某些文件不提交的方法實現(xiàn)

    本文主要介紹了idea中如何過濾某些文件不提交,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧
    2023-07-07
  • JAVA基本類型包裝類 BigDecimal BigInteger 的使用

    JAVA基本類型包裝類 BigDecimal BigInteger 的使用

    Java 中預(yù)定義了八種基本數(shù)據(jù)類型,包括:byte,int,long,double,float,boolean,char,short,接下來文章小編將向大家介紹其中幾個類型的內(nèi)容,需要的朋友可以參考下文章
    2021-09-09
  • Java 8 Stream Api 中的 map和 flatMap 操作方法

    Java 8 Stream Api 中的 map和 flatMap 操作方法

    Java 8提供了非常好用的 Stream API ,可以很方便的操作集合。今天通過這篇文章給大家分享Java 8 Stream Api 中的 map和 flatMap 操作方法,需要的朋友可以參考下
    2019-11-11
  • Javaweb 定時器功能代碼實例

    Javaweb 定時器功能代碼實例

    這篇文章主要介紹了Javaweb 定時器功能代碼實例,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友可以參考下
    2020-04-04
  • log4j2使用filter過濾日志方式

    log4j2使用filter過濾日志方式

    這篇文章主要介紹了log4j2使用filter過濾日志方式,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教
    2021-12-12
  • 五分鐘手?jǐn)]一個Spring容器(萌芽版)

    五分鐘手?jǐn)]一個Spring容器(萌芽版)

    Spring的兩大內(nèi)核分別是IOC和AOP,其中最最核心的是IOC。這篇文章主要介紹了五分鐘,手?jǐn)]一個Spring容器的相關(guān)知識,需要的朋友可以參考下
    2022-03-03
  • SpringMVC中Controller層獲取前端請求參數(shù)的方式匯總

    SpringMVC中Controller層獲取前端請求參數(shù)的方式匯總

    這篇文章主要介紹了SpringMVC中Controller層獲取前端請求參數(shù)的幾種方式,本文通過示例代碼給大家介紹的非常詳細(xì),對大家的學(xué)習(xí)或工作具有一定的參考借鑒價值,需要的朋友可以參考下
    2023-08-08
  • Java輕松實現(xiàn)在Word中插入頁眉頁腳的實用指南

    Java輕松實現(xiàn)在Word中插入頁眉頁腳的實用指南

    在現(xiàn)代企業(yè)應(yīng)用中,Java 開發(fā)者經(jīng)常需要處理各種文檔操作,本文將深入探討如何利用功能強(qiáng)大的 Spire.Doc for Java 庫輕松實現(xiàn)各種頁眉頁腳的插入需求,需要的小伙伴可以了解下
    2025-09-09
  • 分布式調(diào)度器之Spring Task 的使用詳解

    分布式調(diào)度器之Spring Task 的使用詳解

    SpringTask是Spring框架中用于任務(wù)調(diào)度的組件,通過簡單的注解就能實現(xiàn)定時任務(wù)的創(chuàng)建和調(diào)度,可以通過配置線程池來實現(xiàn),本文給大家介紹分布式調(diào)度器之Spring Task 的使用,感興趣的朋友跟隨小編一起看看吧
    2024-10-10

最新評論

大名县| 博湖县| 仲巴县| 临泽县| 清涧县| 莱州市| 景洪市| 宝丰县| 高碑店市| 道真| 北京市| 锦屏县| 阜南县| 都安| 临西县| 富平县| 临邑县| 济宁市| 麟游县| 广饶县| 甘泉县| 吉隆县| 册亨县| 镇宁| 宾川县| 泽普县| 科技| 建宁县| 呼伦贝尔市| 商都县| 合川市| 资阳市| 葵青区| 个旧市| 奉节县| 万载县| 谢通门县| 泸州市| 池州市| 铁力市| 阜新市|