91超碰碰碰碰久久久久久综合_超碰av人澡人澡人澡人澡人掠_国产黄大片在线观看画质优化_txt小说免费全本

溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點擊 登錄注冊 即表示同意《億速云用戶服務條款》

怎么深入理解Java內存模型JMM

發布時間:2021-11-20 15:16:35 來源:億速云 閱讀:151 作者:柒染 欄目:軟件技術

這期內容當中小編將會給大家帶來有關怎么深入理解Java內存模型JMM,文章內容豐富且以專業的角度為大家分析和敘述,閱讀完這篇文章希望大家可以有所收獲。

Java 內存模型

Java 內存模型(JMM)是一種抽象的概念,并不真實存在,它描述了一組規則或規范,通過這組規范定義了程序中各個變量(包括實例字段、靜態字段和構成數組對象的元素)的訪問方式。試圖屏蔽各種硬件和操作系統的內存訪問差異,以實現讓 Java 程序在各種平臺下都能達到一致的內存訪問效果。

注意JMM與JVM內存區域劃分的區別:

  • JMM描述的是一組規則,圍繞原子性、有序性和可見性展開;

  • 相似點:存在共享區域和私有區域

主內存與工作內存

處理器上的寄存器的讀寫的速度比內存快幾個數量級,為了解決這種速度矛盾,在它們之間加入了高速緩存。

加入高速緩存帶來了一個新的問題:緩存一致性。如果多個緩存共享同一塊主內存區域,那么多個緩存的數據可能會不一致,需要一些協議來解決這個問題。

怎么深入理解Java內存模型JMM

所有的變量都 存儲在主內存中,每個線程還有自己的工作內存 ,工作內存存儲在高速緩存或者寄存器中,保存了該線程使用的變量的主內存副本拷貝。

線程只能直接操作工作內存中的變量,不同線程之間的變量值傳遞需要通過主內存來完成。

怎么深入理解Java內存模型JMM

數據存儲類型以及操作方式

  • 方法中的基本類型本地變量將直接存儲在工作內存的棧幀結構中;

  • 引用類型的本地變量:引用存儲在工作內存,實際存儲在主內存;

  • 成員變量、靜態變量、類信息均會被存儲在主內存中;

  • 主內存共享的方式是線程各拷貝一份數據到工作內存中,操作完成后就刷新到主內存中。

內存間交互操作

Java 內存模型定義了 8 個操作來完成主內存和工作內存的交互操作。

怎么深入理解Java內存模型JMM

  • read:把一個變量的值從主內存傳輸到工作內存中

  • load:在 read 之后執行,把 read 得到的值放入工作內存的變量副本中

  • use:把工作內存中一個變量的值傳遞給執行引擎

  • assign:把一個從執行引擎接收到的值賦給工作內存的變量

  • store:把工作內存的一個變量的值傳送到主內存中

  • write:在 store 之后執行,把 store 得到的值放入主內存的變量中

  • lock:作用于主內存的變量

  • unlock

指令重排序的條件

  • 在單線程環境下不能改變程序的運行結果;

  • 存在數據依賴關系的不允許重排序;

  • 無法通過Happens-before原則推到出來的,才能進行指令的重排序。

內存模型三大特性

1. 原子性

Java 內存模型保證了 read、load、use、assign、store、write、lock 和 unlock 操作具有原子性,例如對一個 int 類型的變量執行 assign 賦值操作,這個操作就是原子性的。但是 Java 內存模型允許虛擬機將沒有被 volatile 修飾的 64 位數據(long,double)的讀寫操作劃分為兩次 32 位的操作來進行,即 load、store、read 和 write 操作可以不具備原子性。

有一個錯誤認識就是,int 等原子性的類型在多線程環境中不會出現線程安全問題。前面的線程不安全示例代碼中,cnt 屬于 int 類型變量,1000 個線程對它進行自增操作之后,得到的值為 997 而不是 1000。

為了方便討論,將內存間的交互操作簡化為 3 個:load、assign、store。

下圖演示了兩個線程同時對 cnt 進行操作,load、assign、store 這一系列操作整體上看不具備原子性,那么在 T1 修改 cnt 并且還沒有將修改后的值寫入主內存,T2 依然可以讀入舊值。可以看出,這兩個線程雖然執行了兩次自增運算,但是主內存中 cnt 的值最后為 1 而不是 2。因此對 int 類型讀寫操作滿足原子性只是說明 load、assign、store 這些單個操作具備原子性。

怎么深入理解Java內存模型JMM

AtomicInteger 能保證多個線程修改的原子性。

怎么深入理解Java內存模型JMM

使用 AtomicInteger 重寫之前線程不安全的代碼之后得到以下線程安全實現:

public class AtomicExample {    private AtomicInteger cnt = new AtomicInteger();    public void add() {
        cnt.incrementAndGet();
    }    public int get() {        return cnt.get();
    }
}復制代碼
public static void main(String[] args) throws InterruptedException {
    final int threadSize = 1000;
    AtomicExample example = new AtomicExample(); // 只修改這條語句
    final CountDownLatch countDownLatch = new CountDownLatch(threadSize);
    ExecutorService executorService = Executors.newCachedThreadPool();    for (int i = 0; i < threadSize; i++) {
        executorService.execute(() -> {
            example.add();
            countDownLatch.countDown();
        });
    }
    countDownLatch.await();
    executorService.shutdown();
    System.out.println(example.get());
}復制代碼
1000復制代碼

除了使用原子類之外,也可以使用 synchronized 互斥鎖來保證操作的原子性。它對應的內存間交互操作為:lock 和 unlock,在虛擬機實現上對應的字節碼指令為 monitorenter 和 monitorexit。

public class AtomicSynchronizedExample {    private int cnt = 0;    public synchronized void add() {
        cnt++;
    }    public synchronized int get() {        return cnt;
    }
}復制代碼
public static void main(String[] args) throws InterruptedException {
    final int threadSize = 1000;
    AtomicSynchronizedExample example = new AtomicSynchronizedExample();
    final CountDownLatch countDownLatch = new CountDownLatch(threadSize);
    ExecutorService executorService = Executors.newCachedThreadPool();    for (int i = 0; i < threadSize; i++) {
        executorService.execute(() -> {
            example.add();
            countDownLatch.countDown();
        });
    }    countDownLatch.await();    executorService.shutdown();    System.out.println(example.get());
}復制代碼
1000復制代碼

2. 可見性

可見性指當一個線程修改了共享變量的值,其它線程能夠立即得知這個修改。Java 內存模型是通過在變量修改后將新值同步回主內存,在變量讀取前從主內存刷新變量值來實現可見性的。JMM 內部的實現通常是依賴于所謂的 內存屏障 ,通過 禁止某些重排序 的方式,提供內存 可見性保證 ,也就是實現了 各種 happen-before 規則 。與此同時,更多復雜度在于,需要盡量確保各種編譯器、各種體系結構的處理器,都能夠提供一致的行為。

主要有有三種實現可見性的方式:

  • volatile,會 強制 將該變量自己和當時其他變量的狀態都 刷出緩存 。

  • synchronized,對一個變量執行 unlock 操作之前,必須把變量值同步回主內存。

  • final,被 final 關鍵字修飾的字段在構造器中一旦初始化完成,并且沒有發生 this 逃逸(其它線程通過 this 引用訪問到初始化了一半的對象),那么其它線程就能看見 final 字段的值。

對前面的線程不安全示例中的 cnt 變量使用 volatile 修飾,不能解決線程不安全問題,因為 volatile 并不能保證操作的原子性。

3. 有序性

有序性是指:在本線程內觀察,所有操作都是有序的。在一個線程觀察另一個線程,所有操作都是無序的,無序是因為發生了指令重排序。在 Java 內存模型中,允許編譯器和處理器對指令進行重排序,重排序過程不會影響到單線程程序的執行,卻會影響到多線程并發執行的正確性。

volatile 關鍵字通過添加內存屏障的方式來禁止指令重排,即重排序時不能把后面的指令放到內存屏障之前。

也可以通過 synchronized 來保證有序性,它保證每個時刻只有一個線程執行同步代碼,相當于是讓線程順序執行同步代碼。

先行發生原則(Happen-Before)

JSR-133內存模型使用先行發生原則在Java內存模型中保證多線程操作 可見性 的機制,也是對早期語言規范中含糊的可見性概念的一個精確定義。上面提到了可以用 volatile 和 synchronized 來保證有序性。除此之外,JVM 還規定了先行發生原則,讓一個操作 無需控制 就能先于另一個操作完成。

由于 指令重排序 的存在,兩個操作之間有happen-before關系, 并不意味著前一個操作必須要在后一個操作之前執行。 僅僅要求前一個操作的執行結果對于后一個操作是可見的,并且前一個操作 按順序 排在第二個操作之前。

1. 單一線程原則(程序員順序規則)

Single Thread rule

在一個線程內,在程序前面的操作先行發生于后面的操作。

怎么深入理解Java內存模型JMM

2. 管程鎖定規則(監視器鎖規則)

Monitor Lock Rule

一個 unlock(解鎖) 操作 先行發生于 后面對同一個鎖的 lock(加鎖) 操作。

怎么深入理解Java內存模型JMM

3. volatile 變量規則

Volatile Variable Rule

對一個 volatile 變量的 寫操作 先行發生于后面對這個變量的 讀操作 。

怎么深入理解Java內存模型JMM

4. 線程啟動規則

Thread Start Rule

Thread 對象的 start() 方法調用先行發生于此線程的每一個動作。

怎么深入理解Java內存模型JMM

5. 線程加入規則

Thread Join Rule

Thread 對象的結束先行發生于 join() 方法返回。

怎么深入理解Java內存模型JMM

6. 線程中斷規則

Thread Interruption Rule

對線程 interrupt() 方法的調用先行發生于被中斷線程的代碼檢測到中斷事件的發生,可以通過 interrupted() 方法檢測到是否有中斷發生。

7. 對象終結規則

Finalizer Rule

一個對象的初始化完成(構造函數執行結束)先行發生于它的 finalize() 方法的開始。

8. 傳遞性

Transitivity

如果操作 A 先行發生于操作 B,操作 B 先行發生于操作 C,那么操作 A 先行發生于操作 C。

上述就是小編為大家分享的怎么深入理解Java內存模型JMM了,如果剛好有類似的疑惑,不妨參照上述分析進行理解。如果想知道更多相關知識,歡迎關注億速云行業資訊頻道。

向AI問一下細節

免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。

AI

湖南省| 丰城市| 喜德县| 通道| 九台市| 南京市| 边坝县| 辽宁省| 皋兰县| 宁明县| 南溪县| 黔南| 张家港市| 宜良县| 隆子县| 土默特左旗| 东海县| 龙海市| 盘锦市| 武强县| 香港| 久治县| 武功县| 碌曲县| 五寨县| 科技| 盐边县| 浠水县| 抚远县| 重庆市| 天门市| 黔南| 石首市| 恭城| 泰安市| 股票| 永安市| 布拖县| 油尖旺区| 西平县| 确山县|