<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          手撕并發(fā)編程:十分鐘搞定Java內存模型

          共 18976字,需瀏覽 38分鐘

           ·

          2023-08-20 06:33

          fbb82f6e0cc031052f6e1e3ee2dfcc4f.webp

          d3d1d40825f538d331ee7bd2ac367d1c.webp

          ?? 導讀

          隨著硬件技術的飛速發(fā)展,多核處理器已經成為計算設備的標配,這使得開發(fā)人員需要掌握并發(fā)編程的知識和技巧,以充分發(fā)揮多核處理器的潛力。然而并發(fā)編程并非易事,它涉及到許多復雜的概念和原理。為了更好地理解并發(fā)編程的內在機制,需要深入研究內存模型及其在并發(fā)編程中的應用。本文將主要以 Java 內存模型來探討并發(fā)編程中 BUG 的源頭和處理這些問題的底層實現(xiàn)原理,助你更好地把握并發(fā)編程的內在機制, 歡迎繼續(xù)閱讀。

          ?? 目錄

          1 并發(fā)編程問題-可見性和有序性 2 并發(fā)編程問題-原子性 3 內存模型與 happens-before 關系   4 內存模型綜述


          01

          并發(fā)編程問題-可見性和有序性
                  
                        private int a, b;
                  
                  
                        private int x, y;
                  
                  
                        public void test() {
                  
                  
                            Thread t1 = new Thread(() -> {
                  
                  
                                a = 1;
                  
                  
                                x = b;
                  
                  
                            });
                  
                  
                            Thread t2 = new Thread(() -> {
                  
                  
                                b = 2;
                  
                  
                                y = a;
                  
                  
                            });
                  
                  
                          // ...start啟動線程,join等待線程
                  
                  
                          assert x == 2;
                  
                  
                          assert y == 1;
                  
                  
                        }
                  
                

          首先我們先看一段代碼,這里定義了兩個共享變量 x 和 y,在兩個線程中分別對 x 和 y 賦值,當同時開啟兩個線程并等待線程執(zhí)行完成,最終結果是否是共享變量 x 等于2并且 y 等于1呢?答案是未可知,即共享變量 x 和 y 可能存在多種執(zhí)行結果。可以看到在并發(fā)編程中,常常會遇到一些與預期不符的結果,導致程序邏輯的失敗。這樣的異常問題,會讓開發(fā)人員感到困惑。但是如果細細探究這些問題的根源,發(fā)現(xiàn)是有跡可循的。


          這個問題的原因主要是兩點:一是處理器和內存對共享變量的處理的速度差異。二是編譯優(yōu)化和處理器優(yōu)化造成代碼指令重排序。前者導致可見性問題,后者導致有序性問題。


                1.1 處理器緩存導致的可見性問題


          761d510fd3234d32a3eda354680b11b3.webp


          如上圖所示,由于處理器和內存的速度差距太大。為了提高處理速度,處理器不直接和內存進行通信,而是先將系統(tǒng)內存的數(shù)據讀到內部緩存 (L1,L2或其他) 后再進行操作。基于局部性原理,處理器在讀取內存數(shù)據時,是一塊塊地讀取,每一小塊數(shù)據也叫緩存行 (cache line) 。當處理器操作完數(shù)據,也不直接寫回內存,而且先寫入緩存中,并將當前緩存標記為臟 (dirty) 。等到當前緩存被替換時,才將數(shù)據寫回內存。這個過程叫寫回策略 (write-back)


          同時為了提高效率,處理器使用寫緩存區(qū) (store buffer) 臨時保存向內存寫入的數(shù)據。寫緩沖區(qū)可以保證指令流水線的持續(xù)運行,同時合并寫緩沖區(qū)中對同一內存地址的多次寫,減少內存總線的占用。但是由于緩沖區(qū)的數(shù)據并非及時寫回內存,且寫緩沖區(qū)僅對自己的處理器可見,其他處理器無法感知當前共享變量已經變更。處理器的讀寫順序與內存實際操作的讀寫順序可能存在不一致。


          5cbfc99190c28652c89cd26666dccc5f.webp


          現(xiàn)在再回來看上面代碼,那么可以得到四種結果:


          結果一: 假設處理器 A 對變量 a 賦值,但沒及時回寫內存。處理器 B 對變量 b 賦值,且及時回寫內存。處理器 A 從內存中讀到變量 b 最新值。那么這時結果是:x 等于2,y 等于0。 結果二: 假設處理器 A 對變量 a 賦值,且及時回寫內存。處理器 B 從內存中讀到變量 a 最新值。處理器 B 對變量 b 賦值,但沒及時回寫內存。那么這時結果是:x 等于0,y 等于1。 結果三: 假設處理器 A 和 B,都沒及時回寫變量 a 和 b 值到內存。那么這時結果是:x 等于0,y 等于0。 結果四: 假設處理器 A 和 B,都及時回寫變量 a 和 b 值到內存,且從內存中讀到變量 a 和 b 的最新值。那么這時結果是:x 等于2,y 等于1。

          從上面可發(fā)現(xiàn)除了第四種情況,其他三種情況都存在對共享變量的操作不可見。所謂可見性,便是當一個線程對某個共享變量的操作,另外一個線程立即可見這個共享變量的變更。


          而從上面推論可以發(fā)現(xiàn),要達到可見性,需要處理器及時回寫共享變量最新值到內存,也需要其他處理器及時從內存中讀取到共享變量最新值。


          因此也可以說只要滿足上述兩個條件。那么就可以保證對共享變 量的操作,在并發(fā)情況下是線程安全的。在 Java 語言中,是通過 volatile 關鍵字實現(xiàn)。volatile 關鍵字并不是 Java 語言的特產,古老的 C 語言里也有,它最原始的意義就是禁用 CPU 緩存。


          對如下共享變量:


                  
                      // instance是volatile變量
                  
                  
                      volatile Singlenton instance = new Singlenton();
                  
                

          轉換成匯編代碼,如下:


                  
                    0x01a3de1d: movb 5 0 x 0, 0 x 1104800(% esi);
                  
                  
                    0x01a3de24: lock addl $ 0 x 0,(% esp);
                  
                

          可以看到 volatile 修飾的共享變量會多出第二行匯編變量,并且多了一個 LOCK 指令。LOCK 前綴的指令在多核處理器會引發(fā)兩件事:


          • 將當前處理器緩存行的數(shù)據寫回到系統(tǒng)內存。
          • 將個寫回內存的操作會使在其他 CPU 里緩存了該內存地址的數(shù)據無效。

          上述的操作是通過總線嗅探和總線仲裁來實現(xiàn)。而基于總線嗅探和總線仲裁,現(xiàn)代處理器逐漸形成了各種緩存一致性協(xié)議,例如 MESI 協(xié)議。


          c2efe6b3c0ad81f2b2ef9a25275396cb.webp


          總之操作系統(tǒng)便是基于上述實現(xiàn),從底層來保證共享變量在并發(fā)情況下的線程安全。而對開發(fā)人員,只需要在恰當時候加上 volatile 關鍵字就可以。


          除了 volatile,也可以使用 synchronized 關鍵字來保證可見性。關于 volatile 和 synchronized 的具體實現(xiàn),會在下篇文章詳細闡述。


                1.2 編譯優(yōu)化導致的有序性問題


          前面講到通過緩存一致性協(xié)議,來保障共享變量的可見性。那么是否還有其他情況,導致對共享變量操作不符合預期結果。可以看下面的代碼:


                  
                        private int a, b;
                  
                  
                        private int x, y;
                  
                  
                    
                      
          public void test() { Thread t1 = new Thread(() -> { x = b; a = 1; });
          Thread t2 = new Thread(() -> { y = a; b = 2; }); // ...start啟動線程,join等待線程 assert x == 2; assert y == 1; }

          假設將線程 t1 的代碼塊從 a = 1;x = b;改成 x = b;a = 1; 。將線程 t2 的代碼塊從b = 2;y = a;改成 y = a;b = 2;。


          對于線程 t1 和 t2 自己來說,代碼的重排序,不會影響當前線程執(zhí)行。但是在多線程并發(fā)執(zhí)行下,會出現(xiàn)如下情況: 假設處理器 A 先將變量 b=0 賦值給 x,再將變量 a 賦值1。 處理器 B 先將變量 a=0 賦值給 y,再將變量 b 賦值2。 那么這時結果是: x 等于0,y 等于0。


          可見代碼的重排序也會影響到程序最終結果。


          代碼和指令的重排序的主要原因有三個,分別為編譯器的重排序,處理器的亂序執(zhí)行,以及內存系統(tǒng)的重排序。后面兩點是處理器優(yōu)化。


          f4823230b382582e4231897058c979fd.webp


          重排序是指編譯器和處理器為了優(yōu)化程序性能而對指令序列進行重新排序的一種手段。重排序需要遵守兩點:


          數(shù)據依賴性: 如果兩個操作之間存在數(shù)據依賴,那么編譯器和處理器不能調整它們的順序。


                  
                    // 寫后讀
                  
                  
                    a = 1;
                  
                  
                    b = a;
                  
                  
                    // 寫后寫
                  
                  
                    a = 1;
                  
                  
                    a = 2;
                  
                  
                    // 讀后寫
                  
                  
                    a = b;
                  
                  
                    b = 1;
                  
                

          上面3種情況,編譯器和處理器不能調整它們的順序,否則將會造成程序語義的改變。


          as-if-serial 語義: 即給程序一個順序執(zhí)行的假象。即經過重排序的執(zhí)行結果要與順序執(zhí)行的結果保持一致。


                  
                    a = 1;
                  
                  
                    b = 2;
                  
                  
                    c = a * b;
                  
                

          如上對變量 a 的賦值和對變量 b 的賦值,不存在數(shù)據依賴關系。因此對變量 a 和 b 重排序不會影響變量 c 的結果。


          但數(shù)據依賴性和 as-if-serial 語義只保證單個處理器中執(zhí)行的指令序列和單個線程中執(zhí)行的操作,并不考慮多處理器和多線程之間的數(shù)據依賴情況。因此在多線程程序中,對存在數(shù)據依賴的操作重排序,可能會改變程序的執(zhí)行結果。因此要避免程序的錯誤的執(zhí)行,便是需要禁止這種編譯和處理器優(yōu)化導致的重排序。


          這種方式叫做內存屏障 (memory barriers) 。內存屏障是一組處理器指令,用戶實現(xiàn)對內存操作的順序限制。以我們日常接觸的 X86_64 架構來說,讀讀 (loadload) 、讀寫 (loadstore) 以及寫寫 (storestore) 內存屏障是空操作 (no-op) ,只有寫讀 (storeload) 內存屏障會被替換成具體指令。


          在 Java 語言中,內存屏障通過 volatile 關鍵字實現(xiàn),禁止被它修飾的變量發(fā)生指令重排序操作:


          • 不允許 volatile 字段寫操作之前的內存訪問被重排序至其之后。
          • 不允許 volatile 字段讀操作之后的內存訪問被重排序至其之前。

                  
                        //  變量a,b通過volatile修飾
                  
                  
                        private volatile int a, b; 
                  
                  
                        private int x, y;
                  
                  
                    
                      
          public void test() { Thread t1 = new Thread(() -> { a = 1; // 編譯器插入storeload內存屏障指令 // 1)禁止代碼和指令重排序 // 2)強制刷新變量a的最新值到內存 x = b; // 1)強制從內存中讀取變量b的最新值 });
          Thread t2 = new Thread(() -> { b = 2; // 編譯器插入storeload內存屏障指令 // 1)禁止代碼和指令重排序 // 2)強制刷新變量b的最新值到內存 y = a; // 1)強制從內存中讀取變量a的最新值 }); // ...start啟動線程,join等待線程 assert x == 2; assert y == 1; }

          可以看到通過 volatile 修飾的變量通過 LOCK 指令和內存屏障,實現(xiàn)共享變量的可見性和避免代碼和指令的重排序,最終保障了程序在多線程情況下的正常執(zhí)行。




          02

          并發(fā)編程問題-原子性
                  
                        private int count = 0;
                  
                  
                  
                        public void test() {
                  
                  
                            List<Thread> ts = new ArrayList<>();
                  
                  
                            for (int i = 0; i < 100; i++) {
                  
                  
                                Thread t = new Thread(() -> {
                  
                  
                                    for (int j = 0; j < 10000; j++) {
                  
                  
                                        count += 1;
                  
                  
                                    }
                  
                  
                                });
                  
                  
                                ts.add(t);
                  
                  
                            }
                  
                  
                            // ...start啟動線程,join等待線程
                  
                  
                            assert count == 100 * 10000;
                  
                  
                        }
                  
                

          對于 Java 這樣的高級語言,一條語句最終會被轉換成多條 CPU 指令完成,例如上面代碼中的 count+=1,至少需要三條 CPU 指令:


          指令1: 把變量 count 從內存加載到 CPU 的寄存器; 指令2: 在寄存器中執(zhí)行+1操作; 指令3: 將結果寫入內存(緩存機制導致可能寫入的是處理器緩存而不是內存)

          那么假設有兩個線程 A 和 B,同時執(zhí)行 count+=1,可能存在如下情況:


          • 線程 A 從內存加載 count 并執(zhí)行 count+=1,同時線程 B 從內存加載 count 并執(zhí)行 count+=1,并同時寫回內存。那么這時結果是:count = 1。
          • 線程 A 從內存加載 count 并執(zhí)行 count+=1,并將 count 結果寫回內存。線程 B 再從內存加載 count 并執(zhí)行 count+=1。那么這時結果是:count = 2。

          可以看到如果要 count 結果正確,要保證 count 讀取,操作,寫入三個過程不被中斷。這個過程,可以稱之為原子操作。原子 (atomic) 本意是“不能被進一步分割的最小粒子”,而原子操作 atomicoperation)  意為“不可被中斷的一個或一系列操作”。


          處理器主要使用基于對緩存加鎖或總線加鎖的方式來實現(xiàn)原子操作:


          總線加鎖: 通過 LOCK#信號鎖住總線 BUS,使當前處理器獨享內存空間。但是此時其他處理器都不能訪問內存其他地址,效率低。 


          e162d7a47ffd7475fbe53926a99b0173.webp


          緩存加鎖: 緩存一致性協(xié)議 (MESI) 。強制當前處理器緩存行失效,并從內存讀取其他處理器回寫的數(shù)據。當有些無法被緩存或跨域多個緩存行的數(shù)據,依然需要使用總線鎖。


          aa2bcbb544cc70dea9a0e36b0a4a37b2.webp


          對于以上兩個機制,處理器底層提供了很多指令來實現(xiàn),其中最重要的是 CMPXCHG 指令。但 CMPXCHG 只在單核處理器下有效,多核處理器依然要加上 LOCK 前綴 (LOCK CMPXCHG) 。利用 CMPXCHG 指令可以通過循環(huán) CAS 方式來實現(xiàn)原子操作。


                  
                    // 判斷當前機器是否是多核處理器
                  
                  
                    int mp = os::is MP();
                  
                  
                    _asm {
                  
                  
                        mov edx, dest
                  
                  
                        mov ecx, exchange value
                  
                  
                        mov eax, compare_value
                  
                  
                        // 這里需要先進行判斷是否為多核處理器
                  
                  
                        LOCK IF MP(mp)
                  
                  
                        // 如果是多核處理器就會在這行指令前加Lock標記
                  
                  
                        cmpxchg dword ptr [edx],ecx
                  
                  
                    
                      }
                    
                  
                


          CAS 即 Compare and Swap。CAS 操作需要輸入兩個數(shù)值,一個舊值 (期望操作前的值) 和一個新值,在操作期間先比較舊值有沒有發(fā)生變化,如果沒有發(fā)生變化,才交換成新值,發(fā)生了變化則不交換。


          Java 語言提供了大量的原子操作類,來實現(xiàn)對應的 CAS 操作。比如 AtomicBoolean,AtomicInteger,AtomicLong 等。


                  
                        private AtomicInteger count = new AtomicInteger(0);
                  
                  
                  
                        public void test() {
                  
                  
                            List<Thread> ts = new ArrayList<>();
                  
                  
                            for (int i = 0; i < 100; i++) {
                  
                  
                                Thread t = new Thread(() -> {
                  
                  
                                    for (int j = 0; j < 10000; j++) {
                  
                  
                                       count.incrementAndGet();
                  
                  
                                    }
                  
                  
                                });
                  
                  
                                ts.add(t);
                  
                  
                            }
                  
                  
                            // ...start啟動線程,join等待線程
                  
                  
                            assert count.get() == 100 * 10000;
                  
                  
                        }
                  
                


          CAS 雖然很高效地解決了原子操作,但是 CAS 也存在一些問題,比如 ABA 問題,循環(huán)時間長開銷大,只能保障一個共享變量的原子操作。關于如上問題解決和 Atomic 包介紹,會在下篇文章詳細闡述。




          03

          內存模型與 happens-before 關系

          前面說到處理器提供了一些特殊指令比如 LOCK,CMPXCHG,內存屏障等來保障多線程情況下的程序邏輯正常執(zhí)行。但這依然存在幾個問題:


          • 處理器底層指令實現(xiàn)細節(jié)復雜難懂,開發(fā)人員需要付出巨大的學習成本。
          • 不同的硬件和操作系統(tǒng),對指令的支持和實現(xiàn)不一樣,需要考慮跨平臺的兼容性。

          • 程序業(yè)務邏輯復雜多變,處理器和線程之間的數(shù)據操作依賴關系也相應更復雜。


          因此高級語言會提供一種抽象的內存模型,用于描述多線程環(huán)境下的內存訪問行為。 無需關心底層硬件和操作系統(tǒng)的具體實現(xiàn)細節(jié),就可以編寫出高效、可移植的并發(fā)程序。對于 Java 語言,這種內存模型便是 Java 內存模型 (Java Memory Model,簡稱 JMM)


          Java 內存模型主要特性是提供了 volatile、synchronized、final 等同步原語,用于實現(xiàn)原子性、可見性和有序性。另一個重要的概念便是 happens-before 關系,用來描述并發(fā)編程中操作之間的偏序關系。除了 Java 語言,包括 golang,c++,rust 等高級語言也實現(xiàn)了自己的 happens-before 關系。


          Java 內存模型的 happens-before 關系是用來描述兩個線程操作的內存可見性。需注意這里的可見性,并不代表 A 線程的執(zhí)行順序一定早于 B 線程, 而是 A 線程對某個共享變量的操作對 B 線程可見。即A線程對變量 a 進行寫操作,B 線程讀取到是變量 a 的變更值。


          Java內存模型定義了主內存 (main memory) ,本地內存 (local memory) ,共享變量等抽象關系,來決定共享變量在多線程之間通信同步方式,即前面所說兩個線程操作的內存可見性。其中本地內存,涵蓋了緩存,寫緩沖區(qū),寄存器以及其他硬件和編譯器優(yōu)化等概念。


          c4e547af182dd7c1823231224fc24187.webp


          如圖所示,如果線程 A 與線程 B 之間要通信的話,必須要經歷下面2個步驟:


          • 線程 A 把本地內存 A 中更新過的共享變量刷新到主內存中。
          • 線程 B 到主內存中去讀取線程 A 之前已更新過的共享變量。

          盡管定義這樣的數(shù)據通信方式,但實際程序的數(shù)據依賴操作是復雜多變的。為了進一步抽象這種線程間的數(shù)據同步方式,Java 內存模型定義了下述線程間的 happens-before 關系:


          程序順序規(guī)則: 單線程內,每個操作 happens-before 于該線程中的任意后續(xù)操作。

          監(jiān)視器鎖規(guī)則: 解鎖操作 happens-before 之后對同一把鎖的加鎖操作。

          volatile 變量規(guī)則: volatile 字段的寫操作 happens-before 之后對同一字段的讀操作。

          傳遞性規(guī)則: 如果 A happens-before B,且 B happens-before C,那么 A happens-before C。

          start()規(guī)則: 如果線程 A 執(zhí)行操作 ThreadB.start(),那么 A 線程的 ThreadB.start()操作 happens-before 于線程 B 中的任意操作。

           join()規(guī)則:如果線程 A 執(zhí)行操作 ThreadB.join()并成功返回,那么線程 B 中的任意操作 happens-before 線程 A 從 ThreadB.join()操作成功返回。


          與開發(fā)人員密切相關的是1、2、3、4四個規(guī)則。其中規(guī)則1滿足了 as-if-serial 語義,即 Java 內存模型允許代碼和指令重排序,只要不影響程序執(zhí)行結果。規(guī)則2和3是通過 synchronized、volatile 關鍵字實現(xiàn)。結合規(guī)則1、2、3來看看規(guī)則4的具體使用,可以看到如下的代碼,程序最終執(zhí)行且得到正確結果:


                  
                       // x, y, z被volatile關鍵字修飾
                  
                  
                        private volatile int x, y, z; 
                  
                  
                  
                        public void test() {
                  
                  
                            Thread a = new Thread(() -> {
                  
                  
                                // 基于程序順序規(guī)則
                  
                  
                                // 沒有數(shù)據依賴關系,可以重排序下面代碼 
                  
                  
                                int i = 0;
                  
                  
                                x = 2;
                  
                  
                                // 基于volatile變量規(guī)則
                  
                  
                                // 編譯器插入storeload內存屏障指令 
                  
                  
                                // 1)禁止代碼和指令重排序
                  
                  
                                // 2)強制刷新變量x的最新值到內存
                  
                  
                            });
                  
                  
                  
                            Thread b = new Thread(() -> {
                  
                  
                                int i = 0;
                  
                  
                                // 存在數(shù)據依賴關系,無法重排序下面代碼
                  
                  
                                // 強制從主內存中讀取變量x的最新值
                  
                  
                                y = x;
                  
                  
                                // 基于volatile變量規(guī)則
                  
                  
                                // 編譯器插入storeload內存屏障指令
                  
                  
                                // 1)禁止代碼和指令重排序
                  
                  
                                // 2)強制刷新變量y的最新值到內存
                  
                  
                                // 3)y = x;可能會被編譯優(yōu)化去除
                  
                  
                                y = 3;
                  
                  
                                // 編譯器插入storeload內存屏障指令
                  
                  
                                // 1)禁止代碼和指令重排序
                  
                  
                                // 2)強制刷新變量y的最新值到內存
                  
                  
                            });
                  
                  
                  
                            Thread c = new Thread(() -> {
                  
                  
                                // 基于程序順序規(guī)則
                  
                  
                                // 沒有數(shù)據依賴關系,可以重排序下面代碼
                  
                  
                                int i = 0;
                  
                  
                                // 基于volatile變量規(guī)則
                  
                  
                                // 強制從主內存中讀取變量x和y的最新值
                  
                  
                                z = x * y;
                  
                  
                                // 編譯器插入storeload內存屏障指令
                  
                  
                                // 1)禁止代碼和指令重排序
                  
                  
                                // 2)強制刷新變量z的最新值到內存
                  
                  
                            });
                  
                  
                            // ...start啟動線程,join等待線程
                  
                  
                            assert z == 6;
                  
                  
                            // 可以看到a線程對變量x變更,b線程對變量y變更,最終對線程c可見
                  
                  
                            // 即滿足傳遞性規(guī)則
                  
                  
                        }
                  
                


                  
                      private  int x, y, z;
                  
                  
                    
                      
          public void test() { Thread a = new Thread(() -> { // synchronized,同步原語,程序邏輯將順序串行執(zhí)行 synchronized (this){ // 基于程序順序規(guī)則 // 沒有數(shù)據依賴關系,可以重排序下面代碼 int i = 0; x = 2; // 基于監(jiān)視器鎖規(guī)則 // 強制刷新變量x的最新值到內存 } }); Thread b = new Thread(() -> { // synchronized,同步原語,程序邏輯將順序串行執(zhí)行 synchronized (this) { int i = 0; // 存在數(shù)據依賴關系,無法重排序下面代碼 // 強制從主內存中讀取變量x的最新值 y = x; // 基于監(jiān)視器鎖規(guī)則 // 1)強制刷新變量y的最新值到內存 // 2)y = x;可能會被編譯優(yōu)化去除 y = 3; // 強制刷新變量y的最新值到內存 } }); Thread c = new Thread(() -> { // synchronized,同步原語,程序邏輯將順序串行執(zhí)行 synchronized (this) { // 基于程序順序規(guī)則 // 沒有數(shù)據依賴關系,可以重排序下面代碼 int i = 0; // 基于監(jiān)視器鎖規(guī)則 // 強制從主內存中讀取變量x和y的最新值 z = x * y; // 強制刷新變量z的最新值到內存 } }); // ...start啟動線程,join等待線程 assert z == 6; // 可以看到a線程對變量x變更,b線程對變量y變更,最終對線程c可見 // 即滿足傳遞性規(guī)則 }




          04

          內存模型綜述

          在本文中,我們對 Java 內存模型進行了全面的概述。Java 內存模型是 Java 虛擬機規(guī)范的一部分,為 Java 開發(fā)人員提供了一種抽象的內存模型,用于描述多線程環(huán)境下的內存訪問行為。


          Java 內存模型關注并發(fā)編程中的原子性、可見性和有序性問題,并提供了一系列同步原語 (如 volatile、synchronized 等) 來實現(xiàn)這些原則。此外,還定義 happens-before 關系,用于描述操作之間的偏序關系,從而確保內存訪問的正確性和一致性。


          Java 內存模型的主要優(yōu)勢在于它為并發(fā)編程提供了基礎,簡化了復雜性。屏蔽不同處理器差異性,在不同的處理器平臺之上呈現(xiàn)了一致的內存模型,并允許一定程度的性能優(yōu)化。這些優(yōu)勢使得 Java 開發(fā)人員可以更容易地編寫出正確、高效、可移植的并發(fā)程序。


          了解 Java 內存模型的原理和實踐對于編寫高質量的 Java 并發(fā)程序至關重要。希望本文能為你提供有關 Java 內存模型的有用信息,幫助你更好地理解并發(fā)編程的內在機制,以及在實際項目中選擇合適的同步原語和策略。如果文章對你有幫助,歡迎轉發(fā)分享。


          -End- 原創(chuàng)作者|楊波


          7f79a7f7ef8d3cc971c40b61d260afe7.webp


          Java 的并發(fā)編程能用在哪些方面?學習并發(fā)編程時有哪些要注意的坑?歡迎分享。我們將選取1則最有意義的評論,送出 騰訊云開發(fā)者-便捷通勤袋 1個(見下圖)。8月24日中午12點開獎。


          7d35c3beb1da6e9655a25c52db5d2161.webp


          c368be6843823bdf9291a90132d14b54.webp

          8c40bb25ea0142d90a2c79665e1e66aa.webp

          111af2b297d786a2857c8c3b56f4abcd.webp

          7e7844a84b4034ae80044511c4d4e79e.webp


          關注并星標騰訊云開發(fā)者

          第一時間看鵝廠技術

          瀏覽 32
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

          分享
          舉報
          評論
          圖片
          表情
          推薦
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

          分享
          舉報
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  久久成人AV电影 | 成人一级黄色片 | AV大香蕉 | 亚洲成人电影导航 | 特级西西444kkk高清视频 |