<listing id="lnlbz"></listing>

      <address id="lnlbz"></address>
      <form id="lnlbz"><th id="lnlbz"><listing id="lnlbz"></listing></th></form>

          <form id="lnlbz"></form>

          <progress id="lnlbz"><nobr id="lnlbz"></nobr></progress>

          <address id="lnlbz"><sub id="lnlbz"><menuitem id="lnlbz"></menuitem></sub></address><listing id="lnlbz"><font id="lnlbz"><cite id="lnlbz"></cite></font></listing><thead id="lnlbz"></thead><rp id="lnlbz"></rp>

          1. 移動端
            訪問手機端
            官微
            訪問官微

            搜索
            取消
            溫馨提示:
            敬愛的用戶,您的瀏覽器版本過低,會導致頁面瀏覽異常,建議您升級瀏覽器版本或更換其他瀏覽器打開。

            Java內存模型原理 你真的理解嗎?

            陳彩華 來源:51CTO 2018-11-23 10:20:09 Java 內存模型 金融安全
            陳彩華     來源:51CTO     2018-11-23 10:20:09

            核心提示在介紹 Java 內存模型之前,我們先了解一下物理計算機中的并發問題,理解這些問題可以搞清楚內存模型產生的背景。物理機遇到的并發問題與虛擬機中的情況有不少相似之處,物理機的解決方案對虛擬機的實現有相當的參考意義。

              最近重新學習了一遍《深入學習 Java 虛擬機》,把之前 Java 內存模型中模糊的知識重新梳理了一遍。

              這篇文章主要介紹模型產生的問題背景,解決的問題,處理思路,相關實現規則,環環相扣,希望讀者看完這篇文章后能對 Java 內存模型體系產生一個相對清晰的理解,知其然知其所以然。

              內存模型產生背景

              在介紹 Java 內存模型之前,我們先了解一下物理計算機中的并發問題,理解這些問題可以搞清楚內存模型產生的背景。

              物理機遇到的并發問題與虛擬機中的情況有不少相似之處,物理機的解決方案對虛擬機的實現有相當的參考意義。

              物理機的并發問題

              硬件的效率問題

              計算機處理器處理絕大多數運行任務都不可能只靠處理器“計算”就能完成,處理器至少需要與內存交互,如讀取運算數據、存儲運算結果,這個 I/O 操作很難消除(無法僅靠寄存器完成所有運算任務)。

              由于計算機的存儲設備與處理器的運算速度有幾個數量級的差距,為了避免處理器等待緩慢的內存完成讀寫操作,現代計算機系統通過加入一層讀寫速度盡可能接近處理器運算速度的高速緩存。

              緩存作為內存和處理器之間的緩沖:將運算需要使用到的數據復制到緩存中,讓運算能快速運行,當運算結束后再從緩存同步回內存之中。

            Java內存模型原理 你真的理解嗎?

              緩存一致性問題

              基于高速緩存的存儲系統交互很好的解決了處理器與內存速度的矛盾,但是也為計算機系統帶來更高的復雜度,因為引入了一個新問題:緩存一致性。

              在多處理器的系統中(或者單處理器多核的系統),每個處理器(每個核)都有自己的高速緩存,而它們有共享同一主內存(Main Memory)。

              當多個處理器的運算任務都涉及同一塊主內存區域時,將可能導致各自的緩存數據不一致。

              為此,需要各個處理器訪問緩存時都遵循一些協議,在讀寫時要根據協議進行操作,來維護緩存的一致性。

            Java內存模型原理 你真的理解嗎?

              代碼亂序執行優化問題

              為了使得處理器內部的運算單元盡量被充分利用,提高運算效率,處理器可能會對輸入的代碼進行亂序執行。

              處理器會在計算之后將亂序執行的結果重組,亂序優化可以保證在單線程下該執行結果與順序執行的結果是一致的,但不保證程序中各個語句計算的先后順序與輸入代碼中的順序一致。

            Java內存模型原理 你真的理解嗎?

              亂序執行技術是處理器為提高運算速度而做出違背代碼原有順序的優化。在單核時代,處理器保證做出的優化不會導致執行結果遠離預期目標,但在多核環境下卻并非如此。

              在多核環境下, 如果存在一個核的計算任務依賴另一個核計算任務的中間結果。

              而且對相關數據讀寫沒做任何防護措施,那么其順序性并不能靠代碼的先后順序來保證,處理器最終得出的結果和我們邏輯得到的結果可能會大不相同。

            Java內存模型原理 你真的理解嗎?

              以上圖為例進行說明,CPU 的 core2 中的邏輯 B 依賴 core1 中的邏輯 A 先執行:

              ·正常情況下,邏輯 A 執行完之后再執行邏輯 B。

              ·在處理器亂序執行優化情況下,有可能導致 flag 提前被設置為 true,導致邏輯 B 先于邏輯 A 執行。

              Java 內存模型的組成分析

              內存模型概念

              為了更好解決上面提到的系列問題,內存模型被總結提出,我們可以把內存模型理解為在特定操作協議下,對特定的內存或高速緩存進行讀寫訪問的過程抽象。

              不同架構的物理計算機可以有不一樣的內存模型,Java 虛擬機也有自己的內存模型。

              Java 虛擬機規范中試圖定義一種 Java 內存模型(Java Memory Model,簡稱 JMM)來屏蔽掉各種硬件和操作系統的內存訪問差異,以實現讓 Java 程序在各種平臺下都能達到一致的內存訪問效果,不必因為不同平臺上的物理機的內存模型的差異,對各平臺定制化開發程序。

              更具體一點說,Java 內存模型提出目標在于,定義程序中各個變量的訪問規則,即在虛擬機中將變量存儲到內存和從內存中取出變量這樣的底層細節。

              此處的變量(Variables)與 Java 編程中所說的變量有所區別,它包括了實例字段、靜態字段和構成數值對象的元素,但不包括局部變量與方法參數,因為后者是線程私有的。

              注:如果局部變量是一個 reference 類型,它引用的對象在 Java 堆中可被各個線程共享,但是 reference 本身在 Java 棧的局部變量表中,它是線程私有的。

              Java 內存模型的組成

              主內存

              Java 內存模型規定了所有變量都存儲在主內存(Main Memory)中(此處的主內存與介紹物理硬件的主內存名字一樣,兩者可以互相類比,但此處僅是虛擬機內存的一部分)。

              工作內存

              每條線程都有自己的工作內存(Working Memory,又稱本地內存,可與前面介紹的處理器高速緩存類比),線程的工作內存中保存了該線程使用到的變量的主內存中的共享變量的副本拷貝。

              工作內存是 JMM 的一個抽象概念,并不真實存在。它涵蓋了緩存,寫緩沖區,寄存器以及其他的硬件和編譯器優化。

              Java 內存模型抽象示意圖如下:

            Java內存模型原理 你真的理解嗎?

              JVM 內存操作的并發問題

              結合前面介紹的物理機的處理器處理內存的問題,可以類比總結出 JVM 內存操作的問題,下面介紹的 Java 內存模型的執行處理將圍繞解決這兩個問題展開。

              工作內存數據一致性

              各個線程操作數據時會保存使用到的主內存中的共享變量副本,當多個線程的運算任務都涉及同一個共享變量時,將導致各自的共享變量副本不一致,如果真的發生這種情況,數據同步回主內存以誰的副本數據為準?

              Java 內存模型主要通過一系列的數據同步協議、規則來保證數據的一致性,后面再詳細介紹。

              指令重排序優化

              Java 中重排序通常是編譯器或運行時環境為了優化程序性能而采取的對指令進行重新排序執行的一種手段。

              重排序分為兩類:編譯期重排序和運行期重排序,分別對應編譯時和運行時環境。

              同樣的,指令重排序不是隨意重排序,它需要滿足以下兩個條件:

              ·在單線程環境下不能改變程序運行的結果。即時編譯器(和處理器)需要保證程序能夠遵守 as-if-serial 屬性。 通俗地說,就是在單線程情況下,要給程序一個順序執行的假象。即經過重排序的執行結果要與順序執行的結果保持一致。

              ·存在數據依賴關系的不允許重排序。

              多線程環境下,如果線程處理邏輯之間存在依賴關系,有可能因為指令重排序導致運行結果與預期不同,后面再展開 Java 內存模型如何解決這種情況。

              Java 內存間的交互操作

              在理解 Java 內存模型的系列協議、特殊規則之前,我們先理解 Java 中內存間的交互操作。

              交互操作流程

              為了更好理解內存的交互操作,以線程通信為例,我們看看具體如何進行線程間值的同步:

            Java內存模型原理 你真的理解嗎?

              線程 1 和線程 2 都有主內存中共享變量 x 的副本,初始時,這 3 個內存中 x 的值都為 0。

              線程 1 中更新 x 的值為 1 之后同步到線程 2 主要涉及兩個步驟:

              ·線程 1 把線程工作內存中更新過的 x 的值刷新到主內存中。

              ·線程 2 到主內存中讀取線程 1 之前已更新過的 x 變量。

              從整體上看,這兩個步驟是線程 1 在向線程 2 發消息,這個通信過程必須經過主內存。

              JMM 通過控制主內存與每個線程本地內存之間的交互,來為各個線程提供共享變量的可見性。

              內存交互的基本操作

              關于主內存與工作內存之間的具體交互協議,即一個變量如何從主內存拷貝到工作內存、如何從工作內存同步回主內存之類的實現細節,Java 內存模型中定義了下面 8 種操作來完成。

              虛擬機實現時必須保證下面介紹的每種操作都是原子的,不可再分的(對于 double 和 long 型的變量來說,load、store、read、和 write 操作在某些平臺上允許有例外)。

            Java內存模型原理 你真的理解嗎?

              8 種基本操作,如下圖:

              ·lock (鎖定) ,作用于主內存的變量,它把一個變量標識為一條線程獨占的狀態。

              ·unlock (解鎖) ,作用于主內存的變量,它把一個處于鎖定狀態的變量釋放出來,釋放后的變量才可以被其他線程鎖定。

              ·read (讀取) ,作用于主內存的變量,它把一個變量的值從主內存傳輸到線程的工作內存中,以便隨后的 load 動作使用。

              ·load (載入) ,作用于工作內存的變量,它把 read 操作從主內存中得到的變量值放入工作內存的變量副本中。

              ·use (使用) ,作用于工作內存的變量,它把工作內存中一個變量的值傳遞給執行引擎,每當虛擬機遇到一個需要使用到變量的值的字節碼指令時就會執行這個操作。

              ·assign (賦值) ,作用于工作內存的變量,它把一個從執行引擎接收到的值賦給工作內存的變量,每當虛擬機遇到一個給變量賦值的字節碼指令時執行這個操作。

              ·store (存儲) ,作用于工作內存的變量,它把工作內存中一個變量的值傳送到主內存中,以便隨后 write 操作使用。

              ·write (寫入) ,作用于主內存的變量,它把 Store 操作從工作內存中得到的變量的值放入主內存的變量中。

              Java 內存模型運行規則

              內存交互基本操作的 3 個特性

              在介紹內存交互的具體的 8 種基本操作之前,有必要先介紹一下操作的 3 個特性。

              Java 內存模型是圍繞著在并發過程中如何處理這 3 個特性來建立的,這里先給出定義和基本實現的簡單介紹,后面會逐步展開分析。

              原子性(Atomicity)

              原子性,即一個操作或者多個操作要么全部執行并且執行的過程不會被任何因素打斷,要么就都不執行。

              即使在多個線程一起執行的時候,一個操作一旦開始,就不會被其他線程所干擾。

              可見性(Visibility)

              可見性是指當多個線程訪問同一個變量時,一個線程修改了這個變量的值,其他線程能夠立即看得到修改的值。

              正如上面“交互操作流程”中所說明的一樣,JMM 是通過在線程 1 變量工作內存修改后將新值同步回主內存,線程 2 在變量讀取前從主內存刷新變量值,這種依賴主內存作為傳遞媒介的方式來實現可見性。

              有序性(Ordering)

              有序性規則表現在以下兩種場景:

              ·線程內,從某個線程的角度看方法的執行,指令會按照一種叫“串行”(as-if-serial)的方式執行,此種方式已經應用于順序編程語言。

              ·線程間,這個線程“觀察”到其他線程并發地執行非同步的代碼時,由于指令重排序優化,任何代碼都有可能交叉執行。 唯一起作用的約束是:對于同步方法,同步塊(synchronized 關鍵字修飾)以及 volatile 字段的操作仍維持相對有序。

              Java 內存模型的一系列運行規則看起來有點繁瑣,但總結起來,是圍繞原子性、可見性、有序性特征建立。

              歸根究底,是為實現共享變量的在多個線程的工作內存的數據一致性,多線程并發,指令重排序優化的環境中程序能如預期運行。

              happens-before 關系

              介紹系列規則之前,首先了解一下 happens-before 關系:用于描述下 2 個操作的內存可見性。如果操作 A happens-before 操作 B,那么 A 的結果對 B 可見。

              happens-before 關系的分析需要分為單線程和多線程的情況:

              ·單線程下的 happens-before,字節碼的先后順序天然包含 happens-before 關系:因為單線程內共享一份工作內存,不存在數據一致性的問題。 在程序控制流路徑中靠前的字節碼 happens-before 靠后的字節碼,即靠前的字節碼執行完之后操作結果對靠后的字節碼可見。 然而,這并不意味著前者一定在后者之前執行。實際上,如果后者不依賴前者的運行結果,那么它們可能會被重排序。

              ·多線程下的 happens-before,多線程由于每個線程有共享變量的副本,如果沒有對共享變量做同步處理,線程 1 更新執行操作 A 共享變量的值之后,線程 2 開始執行操作 B,此時操作 A 產生的結果對操作 B 不一定可見。

              為了方便程序開發,Java 內存模型實現了下述支持 happens-before 關系的操作:

              ·程序次序規則,一個線程內,按照代碼順序,書寫在前面的操作 happens-before 書寫在后面的操作。

              ·鎖定規則,一個 unLock 操作 happens-before 后面對同一個鎖的 lock 操作。

              ·volatile 變量規則,對一個變量的寫操作 happens-before 后面對這個變量的讀操作。

              ·傳遞規則,如果操作 A happens-before 操作 B,而操作 B 又 happens-before 操作 C,則可以得出操作 A happens-before 操作 C。

              ·線程啟動規則,Thread 對象的 start() 方法 happens-before 此線程的每個一個動作。

              ·線程中斷規則,對線程 interrupt() 方法的調用 happens-before 被中斷線程的代碼檢測到中斷事件的發生。

              ·線程終結規則,線程中所有的操作都 happens-before 線程的終止檢測,我們可以通過 Thread.join() 方法結束、Thread.isAlive() 的返回值手段檢測到線程已經終止執行。

              ·對象終結規則,一個對象的初始化完成 happens-before 它的 finalize() 方法的開始。

              內存屏障

              Java 中如何保證底層操作的有序性和可見性?可以通過內存屏障。

              內存屏障是被插入兩個 CPU 指令之間的一種指令,用來禁止處理器指令發生重排序(像屏障一樣),從而保障有序性的。

              另外,為了達到屏障的效果,它也會使處理器寫入、讀取值之前,將主內存的值寫入高速緩存,清空無效隊列,從而保障可見性。

              舉個例子說明:

              Store1;

              Store2;

              Load1;

              StoreLoad; //內存屏障

              Store3;

              Load2;

              Load3;

              對于上面的一組 CPU 指令(Store 表示寫入指令,Load 表示讀取指令,StoreLoad 代表寫讀內存屏障),StoreLoad 屏障之前的 Store 指令無法與 StoreLoad 屏障之后的 Load 指令進行交換位置,即重排序。

              但是 StoreLoad 屏障之前和之后的指令是可以互換位置的,即 Store1 可以和 Store2 互換,Load2 可以和 Load3 互換。

              常見有 4 種屏障:

              ·LoadLoad 屏障:對于這樣的語句 Load1;LoadLoad;Load2,在 Load2 及后續讀取操作要讀取的數據被訪問前,保證 Load1 要讀取的數據被讀取完畢。

              ·StoreStore 屏障:對于這樣的語句 Store1;StoreStore;Store2,在 Store2 及后續寫入操作執行前,保證 Store1 的寫入操作對其他處理器可見。

              ·LoadStore 屏障:對于這樣的語句 Load1;LoadStore;Store2,在 Store2 及后續寫入操作被執行前,保證 Load1 要讀取的數據被讀取完畢。

              ·StoreLoad 屏障:對于這樣的語句 Store1;StoreLoad;Load2,在 Load2 及后續所有讀取操作執行前,保證 Store1 的寫入對所有處理器可見。它的開銷是四種屏障中最大的(沖刷寫緩沖器,清空無效化隊列)。 在大多數處理器的實現中,這個屏障是個萬能屏障,兼具其他三種內存屏障的功能。

              Java 中對內存屏障的使用在一般的代碼中不太容易見到,常見的有 volatile 和 synchronized 關鍵字修飾的代碼塊(后面再展開介紹),還可以通過 Unsafe 這個類來使用內存屏障。

              8 種操作同步的規則

              JMM 在執行前面介紹 8 種基本操作時,為了保證內存間數據一致性,JMM 中規定需要滿足以下規則:

              ·規則 1:如果要把一個變量從主內存中復制到工作內存,就需要按順序的執行 read 和 load 操作,如果把變量從工作內存中同步回主內存中,就要按順序的執行 store 和 write 操作。 但 Java 內存模型只要求上述操作必須按順序執行,而沒有保證必須是連續執行。

              ·規則 2:不允許 read 和 load、store 和 write 操作之一單獨出現。

              ·規則 3:不允許一個線程丟棄它的最近 assign 的操作,即變量在工作內存中改變了之后必須同步到主內存中。

              ·規則 4:不允許一個線程無原因的(沒有發生過任何 assign 操作)把數據從工作內存同步回主內存中。

              ·規則 5:一個新的變量只能在主內存中誕生,不允許在工作內存中直接使用一個未被初始化(load 或 assign )的變量。 即對一個變量實施 use 和 store 操作之前,必須先執行過了 load 或 assign 操作。

              ·規則 6:一個變量在同一個時刻只允許一條線程對其進行 lock 操作,但 lock 操作可以被同一條線程重復執行多次,多次執行 lock 后,只有執行相同次數的 unlock 操作,變量才會被解鎖。所以 lock 和 unlock 必須成對出現。

              ·規則 7:如果對一個變量執行 lock 操作,將會清空工作內存中此變量的值,在執行引擎使用這個變量前需要重新執行 load 或 assign 操作初始化變量的值。

              ·規則 8:如果一個變量事先沒有被 lock 操作鎖定,則不允許對它執行 unlock 操作;也不允許去 unlock 一個被其他線程鎖定的變量。

              ·規則 9:對一個變量執行 unlock 操作之前,必須先把此變量同步到主內存中(執行 store 和 write 操作)。

              看起來這些規則有些繁瑣,其實也不難理解:

              ·規則 1、規則 2,工作內存中的共享變量作為主內存的副本,主內存變量的值同步到工作內存需要 read 和 load 一起使用。 工作內存中的變量的值同步回主內存需要 store 和 write 一起使用,這 2 組操作各自都是一個固定的有序搭配,不允許單獨出現。

              ·規則 3、規則 4,由于工作內存中的共享變量是主內存的副本,為保證數據一致性,當工作內存中的變量被字節碼引擎重新賦值,必須同步回主內存。如果工作內存的變量沒有被更新,不允許無原因同步回主內存。

              ·規則 5,由于工作內存中的共享變量是主內存的副本,必須從主內存誕生。

              ·規則 6、7、8、9,為了并發情況下安全使用變量,線程可以基于 lock 操作獨占主內存中的變量,其他線程不允許使用或 unlock 該變量,直到變量被線程 unlock。

              volatile 型變量的特殊規則

              volatile 的中文意思是不穩定的,易變的,用 volatile 修飾變量是為了保證變量的可見性。

              volatile 的語義

              volatile 主要有下面 2 種語義:

              ·保證可見性

              ·禁止進行指令重排序

              保證可見性, 保證了不同線程對該變量操作的內存可見性。 這里保證可見性不等同于 volatile 變量并發操作的安全性, 保證可見性具體一點解釋:

              ·線程對變量進行修改之后,要立刻回寫到主內存。

              ·線程對變量讀取的時候,要從主內存中讀,而不是從線程的工作內存。

              但是如果多個線程同時把更新后的變量值同時刷新回主內存,可能導致得到的值不是預期結果。

              舉個例子:定義 volatile int count = 0,2 個線程同時執行 count++ 操作,每個線程都執行 500 次,最終結果小于 1000。

              原因是每個線程執行 count++ 需要以下 3 個步驟:

              ·線程從主內存讀取最新的 count 的值。

              ·執行引擎把 count 值加 1,并賦值給線程工作內存。

              ·線程工作內存把 count 值保存到主內存。

              有可能某一時刻 2 個線程在步驟 1 讀取到的值都是 100,執行完步驟 2 得到的值都是 101,最后刷新了 2 次 101 保存到主內存。

              禁止進行指令重排序,具體一點解釋,禁止重排序的規則如下:

              ·當程序執行到 volatile 變量的讀操作或者寫操作時,在其前面的操作的更改肯定全部已經進行,且結果已經對后面的操作可見;在其后面的操作肯定還沒有進行。

              ·在進行指令優化時,不能將在對 volatile 變量訪問的語句放在其后面執行,也不能把 volatile 變量后面的語句放到其前面執行。

              普通的變量僅僅會保證該方法的執行過程中所有依賴賦值結果的地方都能獲取到正確的結果,而不能保證賦值操作的順序與程序代碼中的執行順序一致。

              舉個例子:

              volatile boolean initialized = false;

              // 下面代碼線程A中執行

              // 讀取配置信息,當讀取完成后將initialized設置為true以通知其他線程配置可用

              doSomethingReadConfg();

              initialized = true;

              // 下面代碼線程B中執行

              // 等待initialized 為true,代表線程A已經把配置信息初始化完成

              while(!initialized) {

              sleep();

              }

              // 使用線程A初始化好的配置信息

              doSomethingWithConfig();

              上面代碼中如果定義 initialized 變量時沒有使用 volatile 修飾,就有可能會由于指令重排序的優化,導致線程 A 中最后一句代碼 "initialized = true" 在 “doSomethingReadConfg()” 之前被執行。

              這樣會導致線程 B 中使用配置信息的代碼可能出現錯誤,而 volatile 關鍵字就禁止重排序的語義可以避免此類情況發生。

              volatile 型變量實現原理

            Java內存模型原理 你真的理解嗎?

              具體實現方式是在編譯期生成字節碼時,會在指令序列中增加內存屏障來保證,下面是基于保守策略的 JMM 內存屏障插入策略:

              ·在每個 volatile 寫操作的前面插入一個 StoreStore 屏障。該屏障除了保證了屏障之前的寫操作和該屏障之后的寫操作不能重排序,還會保證了 volatile 寫操作之前,任何的讀寫操作都會先于 volatile 被提交。

              ·在每個 volatile 寫操作的后面插入一個 StoreLoad 屏障。該屏障除了使 volatile 寫操作不會與之后的讀操作重排序外,還會刷新處理器緩存,使 volatile 變量的寫更新對其他線程可見。

              ·在每個 volatile 讀操作的后面插入一個 LoadLoad 屏障。該屏障除了使 volatile 讀操作不會與之前的寫操作發生重排序外,還會刷新處理器緩存,使 volatile 變量讀取的為最新值。

              ·在每個 volatile 讀操作的后面插入一個 LoadStore 屏障。該屏障除了禁止了 volatile 讀操作與其之后的任何寫操作進行重排序,還會刷新處理器緩存,使其他線程 volatile 變量的寫更新對 volatile 讀操作的線程可見。

              volatile 型變量使用場景

              總結起來,就是“一次寫入,到處讀取”,某一線程負責更新變量,其他線程只讀取變量(不更新變量),并根據變量的新值執行相應邏輯。例如狀態標志位更新,觀察者模型變量值發布。

              final 型變量的特殊規則

              我們知道,final 成員變量必須在聲明的時候初始化或者在構造器中初始化,否則就會報編譯錯誤。

              final 關鍵字的可見性是指:被 final 修飾的字段在聲明時或者構造器中,一旦初始化完成,那么在其他線程無須同步就能正確看見 final 字段的值。這是因為一旦初始化完成,final 變量的值立刻回寫到主內存。

              synchronized 的特殊規則

              通過 synchronized 關鍵字包住的代碼區域,對數據的讀寫進行控制:

              讀數據,當線程進入到該區域讀取變量信息時,對數據的讀取也不能從工作內存讀取,只能從內存中讀取,保證讀到的是最新的值。

              寫數據,在同步區內對變量的寫入操作,在離開同步區時就將當前線程內的數據刷新到內存中,保證更新的數據對其他線程的可見性。

              long 和 double 型變量的特殊規則

              Java 內存模型要求 lock、unlock、read、load、assign、use、store、write 這 8 種操作都具有原子性。

              但是對于 64 位的數據類型(long 和 double),在模型中特別定義相對寬松的規定:允許虛擬機將沒有被 volatile 修飾的 64 位數據的讀寫操作分為 2 次 32 位的操作來進行。

              也就是說虛擬機可選擇不保證 64 位數據類型的 load、store、read 和 write 這 4 個操作的原子性。

              由于這種非原子性,有可能導致其他線程讀到同步未完成的“32 位的半個變量”的值。

              不過實際開發中,Java 內存模型強烈建議虛擬機把 64 位數據的讀寫實現為具有原子性。

              目前各種平臺下的商用虛擬機都選擇把 64 位數據的讀寫操作作為原子操作來對待,因此我們在編寫代碼時一般不需要把用到的 long 和 double 變量專門聲明為 volatile。

              總結

              由于 Java 內存模型涉及系列規則,網上的文章大部分就是對這些規則進行解析,但是很多沒有解釋為什么需要這些規則,這些規則的作用。

              其實這是不利于初學者學習的,容易繞進這些繁瑣規則不知所以然,下面談談我的一點學習知識的個人體會:

              學習知識的過程不是等同于只是理解知識和記憶知識,而是要對知識解決的問題的輸入和輸出建立連接。

              知識的本質是解決問題,所以在學習之前要理解問題,理解這個問題要的輸入和輸出,而知識就是輸入到輸出的一個關系映射。

              知識的學習要結合大量的例子來理解這個映射關系,然后壓縮知識,華羅庚說過:“把一本書讀厚,然后再讀薄”,解釋的就是這個道理,先結合大量的例子理解知識,然后再壓縮知識。

              以學習 Java 內存模型為例:

              ·理解問題:明確輸入輸出,首先理解 Java 內存模型是什么,有什么用,解決什么問題 。

              ·理解內存模型系列協議:結合大量例子理解這些協議規則。

              ·壓縮知識:大量規則其實就是通過數據同步協議,保證內存副本之間的數據一致性,同時防止重排序對程序的影響。

              參考文章:

              ·《深入學習Java虛擬機》

              ·深入拆解Java虛擬機

              ·Java核心技術36講

              ·Synchronization and the Java Memory Model ——Doug Lea

              ·深入理解 Java 內存模型

              ·Java內存屏障和可見性

              ·內存屏障與synchronized、volatile的原理

            1024你懂的国产日韩欧美_亚洲欧美色一区二区三区_久久五月丁香合缴情网_99爱之精品网站

            <listing id="lnlbz"></listing>

                <address id="lnlbz"></address>
                <form id="lnlbz"><th id="lnlbz"><listing id="lnlbz"></listing></th></form>

                    <form id="lnlbz"></form>

                    <progress id="lnlbz"><nobr id="lnlbz"></nobr></progress>

                    <address id="lnlbz"><sub id="lnlbz"><menuitem id="lnlbz"></menuitem></sub></address><listing id="lnlbz"><font id="lnlbz"><cite id="lnlbz"></cite></font></listing><thead id="lnlbz"></thead><rp id="lnlbz"></rp>

                      責任編輯:韓希宇

                      免責聲明:

                      中國電子銀行網發布的專欄、投稿以及征文相關文章,其文字、圖片、視頻均來源于作者投稿或轉載自相關作品方;如涉及未經許可使用作品的問題,請您優先聯系我們(聯系郵箱:cebnet@cfca.com.cn,電話:400-880-9888),我們會第一時間核實,謝謝配合。

                      為你推薦

                      猜你喜歡

                      收藏成功

                      確定