1. 程式人生 > >Java基礎:由JVM記憶體模型詳解執行緒安全

Java基礎:由JVM記憶體模型詳解執行緒安全

1.前言

最近在研究JVM記憶體模型和Java基礎知識。主要講的是執行緒共享變數與執行緒私有變數以及如何寫出執行緒安全的程式碼。這裡列出一條規則,“類中的成員變數,也叫例項變數,也叫全域性變數,它是非執行緒安全,是所有執行緒共享的變數,定義在方法中的私有變數是執行緒安全的,是每個執行緒私有的”。那麼我們如何通過JVM記憶體模型來理解這句話。現在彙總知識如下:

2. Java虛擬機器執行時的資料區

在這裡插入圖片描述

通過上面這幅圖片我們可以發現,在JVM虛擬機器執行的時候可以將記憶體區域劃分為以上五個部分:方法區、Java堆、JVM虛擬機器棧、本地方法棧和程式計數器。其中方法區和堆是執行緒共享的。虛擬機器棧、本地方法棧、程式計數器是執行緒私有的。

2.1 Java虛擬機器棧

Java虛擬機器棧就是Java方法的記憶體模型,其中包括區域性變量表、運算元棧、動態連結、以及方法的返回地址。

下面我們補充一下Java中的基本資料型別。

基本型別(primitive types), 共有8種,即int, short, long, byte, float, double, boolean, char(注意,並沒有string的基本型別)。這種型別的定義是通過諸如int a = 3; long b = 255L;的形式來定義的,稱為自動變數。值得注意的是,自動變數存的是字面值,不是類的例項,即不是類的引用,這裡並沒有類的存在。如int a = 3; 這裡的a是一個指向int型別的引用,指向3這個字面值。這些字面值的資料,由於大小可知,生存期可知(這些字面值固定定義在某個程式塊裡面,程式塊退出後,欄位值就消失了),出於追求速度的原因,就存在於棧中。

最後需要補充的是所有物件的引用也都存在於棧中,而實際的物件本身是儲存在堆中的,我們這時候倒可以將引用理解為一個指標,它指向了我們在堆中建立的物件。

下面我們再談一談 棧內資料共享:

棧中的資料可以共享。假設我們同時定義int a = 3; int b = 3; 編譯器先處理int a = 3;首先它會在棧中建立一個變數為a的引用,然後查詢有沒有字面值為3的地址,沒找到,就開闢一個存放3這個字面值的地址,然後將a指向3的地址。接著處理int b = 3;在建立完b的引用變數後,由於在棧中已經有3這個字面值,便將b直接指向3的地址。這樣,就出現了a與b同時均指向3的情況。 特別注意的是,這種字面值的引用與類物件的引用不同。假定兩個類物件的引用同時指向一個物件,如果一個物件引用變數修改了這個物件的內部狀態,那麼另一個物件引用變數也即刻反映出這個變化。相反,通過字面值的引用來修改其值,不會導致另一個指向此字面值的引用的值也跟著改變的情況。

有了以上基礎知識的儲備之後我們再來看一下類中定義在方法中的私有變數是執行緒安全的,是每個執行緒私有的這句話。

public class FunVarShare {

    public void testA(){
        int a;
        Person person=new Person();
    }
}

class Person{
    private String name;
    private int age;
}

我們分析這段程式碼,基本資料型別a和物件person定義在類FunVarShare的testA()方法中。其中a和person引用存在於虛擬機器棧幀中的區域性變量表中。person引用的物件存在於堆中,是執行緒共享的。由於虛擬機器棧是執行緒私有的,每個執行緒呼叫一次方法都會新建立一個物件,這些物件都屬於每個執行緒所私有,所以雖然物件本身存在於堆中,但也並不共享。至於基本型別由於每個執行緒執行時將會把區域性變數放在各自棧幀的區域性變量表中,執行緒間不共享。所有類中定義在方法中的私有變數是執行緒安全的,是執行緒私有的。

2.2 Java堆

堆中儲存的資料,只要記住一句話,所有new出來的變數都儲存在堆中。存在堆中的物件是執行緒共享的。

public class ClassVarShare {
    private int a;
}

我們分析這段程式碼,基本資料型別a定義在ClassVarShare類的成員變數中,由於它存放在Java堆中。堆中的物件是執行緒共享的,所有它是執行緒不安全的。所有類中的成員變數,也叫例項變數,也叫全域性變數,它是非執行緒安全,是所有執行緒共享的變數。