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

Java中的原子性、可見性和有序性示例詳解

 更新時間:2026年01月21日 10:44:44   作者:gis分享者  
原子性、可見性、有序性是多線程編程中最重要的幾個知識點,由于多線程情況復(fù)雜,如何讓每個線程能看到正確的結(jié)果,這是非常重要的,這篇文章主要介紹了Java中原子性、可見性和有序性的相關(guān)資料,需要的朋友可以參考下

一、回答重點

原子性、可見性、有序性是 Java 并發(fā)編程的三大核心特性,任何并發(fā) bug 基本都能歸到這三類里面。

1 原子性

原子性指一個操作要么全部執(zhí)行完,要么壓根沒執(zhí)行,中間不會被其他線程打斷。比如 i++ 這個操作看著像一行代碼,實際上是讀取、加 1、寫回三個步驟,多線程環(huán)境下就可能出問題。

2 可見性

可見性指一個線程修改了共享變量,其他線程能立刻看到最新值。CPU 有自己的高速緩存,線程修改的值可能還躺在緩存里沒刷回主內(nèi)存,別的線程就讀到了舊值。

3 有序性

有序性指程序執(zhí)行順序和代碼寫的順序一致。編譯器和 CPU 為了性能會對指令做重排序,單線程下沒問題,多線程就可能出現(xiàn)詭異的 bug。

下面用一個經(jīng)典的例子演示這三個問題是怎么搞出 bug 的:

public class Singleton {
    private static Singleton instance;
    
    public static Singleton getInstance() {
        if (instance == null) {                 // 第一次檢查
            synchronized (Singleton.class) {
                if (instance == null) {         // 第二次檢查
                    instance = new Singleton(); // 問題就出在這
                }
            }
        }
        return instance;
    }
}

這段雙重檢查鎖定看起來沒毛病,但 instance = new Singleton() 這行代碼實際上分三步:分配內(nèi)存空間、初始化對象、把引用指向內(nèi)存地址。CPU 可能把第 2 步和第 3 步重排序,導(dǎo)致另一個線程拿到一個還沒初始化完的對象,直接空指針。解決辦法就是給 instance 加上 volatile。

二、擴展知識

1. 原子性的保障手段

Java 里保證原子性主要靠兩種方式:鎖和 CAS。

synchronized 和 Lock 是最直接的手段,進入臨界區(qū)的線程獨占資源,其他線程只能干等著。但鎖的開銷不小,線程切換、阻塞喚醒都是重量級操作。

CAS 是一種樂觀鎖思路,底層依賴 CPU 的 cmpxchg 指令。比如 AtomicInteger 的 incrementAndGet,它會不斷嘗試"比較當(dāng)前值是否等于預(yù)期值,等于就更新",失敗就重試。CAS 避免了線程阻塞,但在競爭激烈時會瘋狂自旋,CPU 空轉(zhuǎn)。

// AtomicInteger 的自增底層就是 CAS
AtomicInteger count = new AtomicInteger(0);
count.incrementAndGet(); // 內(nèi)部循環(huán) CAS 直到成功

JDK 8 引入了 LongAdder,思路是把一個變量拆成多個 Cell,不同線程操作不同的 Cell,最后匯總。高并發(fā)場景下比 AtomicLong 快很多,Elasticsearch 的計數(shù)器就用的這種方案。

2. 可見性的底層原理

可見性問題的根源在于 CPU 緩存。現(xiàn)代 CPU 都有 L1、L2、L3 多級緩存,每個核心有自己的 L1/L2,線程讀寫變量時優(yōu)先操作緩存。如果線程 A 在 CPU0 上改了變量,值還在 L1 緩存里,線程 B 在 CPU1 上讀的還是舊值。

volatile 的作用就是強制刷新緩存。寫 volatile 變量時,JVM 會插入 StoreStore 屏障和 StoreLoad 屏障,把緩存里的數(shù)據(jù)刷回主內(nèi)存;讀 volatile 變量時,會插入 LoadLoad 屏障和 LoadStore 屏障,強制從主內(nèi)存讀取。

synchronized 也能保證可見性。線程退出 synchronized 塊時,會把所有修改刷回主內(nèi)存;進入 synchronized 塊時,會清空本地緩存,強制從主內(nèi)存重新加載。所以 synchronized 塊里的代碼不需要額外加 volatile。

final 字段也有可見性保證。JVM 保證對象構(gòu)造完成后,final 字段的值對其他線程可見,不需要額外同步。這就是為什么 String 的 value 數(shù)組是 final 的。

3. 有序性與指令重排

編譯器和 CPU 都會做指令重排序,目的是充分利用 CPU 流水線,提高執(zhí)行效率。

編譯器重排是在生成字節(jié)碼或機器碼時調(diào)整指令順序。比如兩條不相關(guān)的賦值語句,編譯器可能調(diào)換順序來優(yōu)化寄存器使用。

CPU 重排更常見,現(xiàn)代 CPU 都是亂序執(zhí)行。CPU 會把沒有數(shù)據(jù)依賴的指令并行執(zhí)行,執(zhí)行完再按原始順序提交結(jié)果。單線程下完全沒問題,因為 CPU 保證了 as-if-serial 語義,執(zhí)行結(jié)果和順序執(zhí)行一樣。

但多線程環(huán)境下,A 線程的兩條指令對 A 來說沒依賴,對 B 線程可能就有依賴。經(jīng)典的例子是上面的雙重檢查鎖定,對象初始化和引用賦值對構(gòu)造線程沒依賴,但其他線程可能在初始化完成前就拿到了引用。

JMM 定義了 happens-before 規(guī)則來約束重排序。只要操作 A happens-before 操作 B,那 A 的結(jié)果對 B 一定可見,A 的執(zhí)行順序也一定在 B 之前。

4. 三大特性的實現(xiàn)方式對比

優(yōu)缺點對比

特性volatilesynchronizedLockAtomic
原子性不保證保證保證保證
可見性保證保證保證保證
有序性禁止重排序臨界區(qū)內(nèi)有序臨界區(qū)內(nèi)有序單個操作有序
性能最輕中等可控較輕
適用場景狀態(tài)標(biāo)記臨界區(qū)保護需要精細控制計數(shù)器

三、面試官追問

提問:volatile 能保證原子性嗎?為什么 volatile int count 的 count++ 不是線程安全的??

回答:volatile 只保證可見性和禁止重排序,不保證原子性。count++ 實際上是讀取、加 1、寫回三個步驟,多個線程可能同時讀到同一個值,各自加 1 后寫回,結(jié)果就少加了。想要原子自增得用 AtomicInteger 或者 synchronized。

提問:synchronized 和 volatile 在底層實現(xiàn)上有什么區(qū)別?

回答:volatile 是通過內(nèi)存屏障實現(xiàn)的,寫操作插入 StoreStore 和 StoreLoad 屏障,讀操作插入 LoadLoad 和 LoadStore 屏障,純粹靠 CPU 指令保證,不涉及鎖。synchronized 底層是 monitor 機制,JVM 會在對象頭里記錄鎖狀態(tài),涉及到偏向鎖、輕量級鎖、重量級鎖的升級過程,重量級鎖要靠操作系統(tǒng)的互斥量,有線程切換開銷。

提問:為什么雙重檢查鎖定的單例需要加 volatile,不加會出什么問題?

回答:new 對象分三步:分配內(nèi)存、初始化、引用賦值。不加 volatile 的話,CPU 可能把初始化和引用賦值重排序,另一個線程可能在第一次 null 檢查時拿到一個非 null 但還沒初始化完的引用,直接用就空指針或者數(shù)據(jù)錯亂。volatile 禁止了這種重排序。

總結(jié)

到此這篇關(guān)于Java中的原子性、可見性和有序性的文章就介紹到這了,更多相關(guān)Java原子性、可見性和有序性內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!

相關(guān)文章

  • 淺談maven的jar包和war包區(qū)別 以及打包方法

    淺談maven的jar包和war包區(qū)別 以及打包方法

    下面小編就為大家分享一篇淺談maven的jar包和war包區(qū)別 以及打包方法,具有很好的參考價值,希望對大家有所幫助
    2017-11-11
  • Java的JDBC中Statement與CallableStatement對象實例

    Java的JDBC中Statement與CallableStatement對象實例

    這篇文章主要介紹了Java的JDBC中Statement與CallableStatement對象實例,JDBC是Java編程中用于操作數(shù)據(jù)庫的API,需要的朋友可以參考下
    2015-12-12
  • Java基于Tcp/ip連接的多人交互聊天室

    Java基于Tcp/ip連接的多人交互聊天室

    這篇文章主要為大家詳細介紹了Java基于Tcp/ip連接的多人交互聊天室,文中示例代碼介紹的非常詳細,具有一定的參考價值,感興趣的小伙伴們可以參考一下
    2018-07-07
  • Spring Boot配置Swagger的實現(xiàn)代碼

    Spring Boot配置Swagger的實現(xiàn)代碼

    這篇文章主要介紹了Spring Boot配置Swagger的實現(xiàn)代碼,小編覺得挺不錯的,現(xiàn)在分享給大家,也給大家做個參考。一起跟隨小編過來看看吧
    2018-12-12
  • Java?C++題解eetcode940不同的子序列?II

    Java?C++題解eetcode940不同的子序列?II

    這篇文章主要為大家介紹了Java?C++題解eetcode940不同的子序列?II實現(xiàn)示例,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步,早日升職加薪
    2022-10-10
  • Java解析JSON數(shù)據(jù)時報錯問題解決方案

    Java解析JSON數(shù)據(jù)時報錯問題解決方案

    這篇文章主要介紹了Java解析JSON數(shù)據(jù)時報錯問題解決方案,文中通過示例代碼介紹的非常詳細,對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友可以參考下
    2020-10-10
  • Spring使用注解方式處理事務(wù)

    Spring使用注解方式處理事務(wù)

    這篇文章主要為大家詳細介紹了Spring使用注解方式處理事務(wù),文中示例代碼介紹的非常詳細,具有一定的參考價值,感興趣的小伙伴們可以參考一下
    2022-08-08
  • SpringBoot靜態(tài)資源路徑配置及主頁顯示

    SpringBoot靜態(tài)資源路徑配置及主頁顯示

    這篇文章主要介紹了SpringBoot靜態(tài)資源路徑配置及主頁顯示,文中通過示例代碼介紹的非常詳細,對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友可以參考下
    2020-04-04
  • java中ThreadLocal取不到值的兩種原因

    java中ThreadLocal取不到值的兩種原因

    這篇文章主要介紹了java中ThreadLocal取不到值的兩種原因,文中通過示例代碼介紹的非常詳細,對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧
    2020-11-11
  • Spring創(chuàng)建Bean的過程Debug的詳細流程

    Spring創(chuàng)建Bean的過程Debug的詳細流程

    這篇文章主要介紹了Spring創(chuàng)建Bean的過程Debug的流程,本文通過圖文并茂的形式給大家介紹的非常詳細,對大家的學(xué)習(xí)或工作具有一定的參考借鑒價值,需要的朋友可以參考下
    2020-11-11

最新評論

宜章县| 阜南县| 沈丘县| 农安县| 衡山县| 五指山市| 洮南市| 石屏县| 侯马市| 鄂托克前旗| 桃源县| 上饶市| 海安县| 新民市| 崇信县| 富源县| 宁远县| 隆德县| 都江堰市| 泰安市| 福建省| 肇庆市| 七台河市| 沙洋县| 房山区| 余庆县| 庆城县| 上虞市| 渭南市| 永修县| 霍城县| 淮滨县| 文昌市| 云南省| 安达市| 胶南市| 木里| 三明市| 临湘市| 贵州省| 贡觉县|