【Java虛擬機】Java內存模型與線程

前言

  併發處理的廣泛應用使得Amdahl定律代替摩爾定律成爲計算機性能發展源動力的根本原因,也是人類壓榨計算機運算能力的最有力武器。
  注:Amdahl定律通過系統中並行化與串行化的比重來描述多處理器系統能夠獲得的運算加速能力,摩爾定律則用與描述處理器晶體管數量與運行效率之間的發展關係。這兩個定律的更替代表了近年來硬件發展從追求處理器頻率到追求多核心並行處理的發展過程。

導圖概述

在這裏插入圖片描述

Java內存模型

一、背景

  1.I/O操作
  大多數的運算任務,處理器至少要與內存交互,如讀取運算數據、存儲運算結果等I/O操作。由於計算機的存儲設備與處理器的運算速度有幾個數量級的這種硬件效率差距,所以現代計算機需要加入接近處理器運算速度的高速緩存Cache來作爲內存與處理器之間的緩衝。
  2.緩存一致性
  在多處理器系統中,每個處理器都有自己的高速緩存,而它們共享同一個主內存Main Memory,爲保證同步回主內存時數據一致性,各個處理器訪問緩存時需要遵循一些協議,如MSI、MESI、MOSI等。
  3.內存模型
  內存模型可以理解爲在特定的操作協議下,對特定的內存或高速緩存進行讀寫訪問的過程抽象,如圖所示,它們之間的關係。
  不同架構的物理機器可以擁有不一樣的內存模型,而Java虛擬機也有自己的內存模型,並且文中介紹的內存訪問與硬件的緩存訪問操作具有很高的可比性。

在這裏插入圖片描述

二、Java內存模型

  1.產生
  Java虛擬機規範中定義一種Java內存模型(Java Memory Model JMM)來屏蔽各種硬件和操作系統的內存訪問差異,以實現Java程序在各種平臺下都能達到一致的內存訪問效果。
  2.主要目標
  定義程序中各個變量的訪問規則,在虛擬機中將變量存儲到內存和從內存中取出變量的底層細節。變量包括實例字段、靜態字段、數組對象的元素,不包括局部變量與方法參數,因爲後者是線程私有的,不會被共享。
  3.三部分
  主內存存儲所有的變量,主內存僅是虛擬機內存的一部分,不同於物理硬件的主內存;工作內存保存了該線程使用到的變量的主內存的副本拷貝,線程對變量的所有操作必須在工作內存中進行,不能直接讀寫主內存的變量。線程間的變量值傳遞需要通過主內存完成。
  線程、主內存、工作內存三者的交互關係如下圖所示:

在這裏插入圖片描述


   從變量、主內存、工作內存的定義看,主內存主要對應於Java堆中的對象實例數據部分,工作內存對應於虛擬機棧中的部分區域。從更低層次上說,主內存直接對應於物理硬件的內存,爲了獲取更好的運行速度,虛擬機可能會讓工作內存優先存儲於寄存器和高速緩存中,因爲程序運行時主要訪問讀寫的是工作內存。

內存間交互操作

一、原子性特徵:8種操作

  1.作用於主內存的變量操作
  lock(鎖定)、unlock(解鎖)、read(讀取)、write(寫入)
  2.作用於工作內存的變量操作
  load(載入)、use(使用)、assign(賦值)、store(存儲)

二、有序性、可見性特徵--volatile型變量、先行發生原則

  1.地位:是Java虛擬機提供的最輕量級的同步機制
  2.兩種特性
  (1)可見性:指一條線程修改了這個變量的值,新值對於其他線程來說是立即得知的。volatile變量也可以存在不一致的情況,由於每次使用之前都要先刷新,執行引擎看不到不一致的情況,因此可以認爲不存在一致性問題。
  Java運算並非原子操作,導致volatile在併發下是不安全的,如下所示代碼:
public class VolatileTest {
    public static volatile int race=0;
    
    public static void increase() {
        race++;
    }
    private static final int THREADS_COUNT=20;

    public static void main(String[] args) {
        Thread[] threads=new Thread[THREADS_COUNT];
        for (int i = 0; i <threads.length ; i++) {
            threads[i]=new Thread(new Runnable() {
                @Override
                public void run() {
                    for (int j = 0; j <10000 ; j++) {
                        increase();
                        System.out.println(race+" 一個線程中的值,i="+j);
                    }
                }
            });
            threads[i].start();
        }
        System.out.println(race);
    }
}

  這段代碼發起了20個線程,每個線程都對race變量進行10000次自增操作,正確結果應該是200000,但是運行後發現,結果都是小於200000的數字。客觀的說,即使編譯出來只有一條字節碼指令,解釋器將要運行許多行代碼才能實現它的語義;如果編譯執行,一條字節碼指令也可能轉化成若干條本地機器碼指令。這說明執行這條指令不是一個原子操作。

在這裏插入圖片描述
  使用volatile關鍵字需要滿足兩條規則,否則需要synchronized或java.until.concurrent中原子類保證原子性。

  • 運算結果不依賴變量的當前值,或者能夠確保只有單一的線程修改變量的值。
  • 變量不需要與其他的狀態變量共同參與不變約束。

  (2)禁止指令重排序:volatile屏蔽指令重排序的語義在JDK1.5中才被完全修復。關鍵的變化在於,volatile修飾的變量,賦值後多執行了一個相當於內存屏障的操作。重排序時不能把後面的指令重排序到內存屏障之前的位置。
  因此,volatile變量讀操作的性能消耗和普通變量沒有差別,但是寫操作會慢一些,因爲它需要在本地代碼中插入許多內存屏障指令保證處理器不發生亂序執行。
  3.long和double型變量的特殊規則
    64位的數據類型(long、double),允許虛擬機在沒有volatile修改的情況下讀寫操作劃分爲兩次32位的操作進行。可以不保證load、store、read和write的原子性。

三、java與線程

  1.線程
  線程是比進程更輕量級的調度執行單位,可以把一個進程的資源分配和執行調度分開,各個線程可以共享進程資源(內存地址、文件I/O等),又可以獨立調度。
  2.線程實現-3種方式
  內核線程:操作系統內核通過操作調度器對象成進行調度,並負責將線程的任務映射到各個處理器上。
  每個內核線程相當於內核的分身,程序一般不會直接使用內核線程,通過內核線程的一種高級接口-輕量級進程(Light Weight Process,LWP )

在這裏插入圖片描述
  用戶線程:完全建立在用戶空間的線程庫上,系統內核不能感知線程存在。
在這裏插入圖片描述
  用戶線程加輕量級進程混合實現

在這裏插入圖片描述
  3.Java線程調度
  指爲線程分配處理器使用權的過程,主要有兩種協同式線程調度和搶佔式線程調度。協同式線程調度,由線程本身控制執行時間;搶佔式線程調度由系統分配執行時間。
  4.5種狀態
  新建New、運行Runnable、等待(Waiting、Timed Waiting)、阻塞Blocked、結束Terminated
在這裏插入圖片描述

小結

  主要了解了Java內存模型的結構及操作,原子性、可見性、有序性在Java內存模型中的體現。
感謝您的訪問!
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章