JVM線程內存模型
根據上述兩篇文章的說明,大概知道jvm是如何分配內存並且運行起來的。
現在我們來看既然對於JVM的內存模型來說,方法區和堆是線程共享的 但是對於jvm棧,棧幀,計數器等是線程獨享的,很顯然,當兩個線程如果同時操作方法區中的靜態變量n,爲什麼會產生併發問題?那如何解決併發問題?
首先來看下JVM的線程模型
首先爲什麼線程模型會長這個樣子?
在一般理解的情況下,線程是有其獨享的內存在運行時如果需要操作共享變量需要將方法區存儲的變量拿到線程內存中進行操作,操作完成之後賦值方法區變量。可是實際情況並非如此!隨着cpu的快速發展,直接從內存上獲取數據的效率低下,無法滿足現在cpu的高速運轉,於是每個cpu都會存在多級緩存機制,如下圖所示
那cpu和線程又有什麼關係呢?首先線程是如何運行的呢?拿單核的CPU舉例:cpu在同一節點只能做一個操作,爲了cpu有多任務處理的能力, 可以理解爲將cpu時間劃分爲時間片,cpu在某一個時間片執行某一個線程,循環往復。
接着上述所說可以理解線程就是單個的cpu,那當cpu執行代碼時首先會將用到的變量緩存到高速緩存區,然後當需要操作變量時會從高速緩存區取值而不是直接從內存地址上取值。
這種模式明顯加快了數據訪問速度,但是也產生了併發問題!
那接下來看一段代碼
public static boolean flag = false;
public static void main(String[] args) throws InterruptedException {
new Thread(new Runnable() {
@Override
public void run() {
while (!flag){
}
System.out.println("線程1結束");
}
}).start();
Thread.sleep(5000);
new Thread(new Runnable() {
@Override
public void run() {
flag = true;
System.out.println("線程2結束");
}
}).start();
}
}
當代碼運行起來之後發現:當線程二結束之後,線程1仍然在while死循環!
那說明當線程二執行完後,雖然flag的值改變了 但是並沒有同步到線程中,對於線程一來說flag的值仍然是false
接下來看另外一段代碼
public static int flag = 0;
public static void main(String[] args) throws InterruptedException {
new Thread(new Runnable() {
@Override
public void run() {
for (int i = 0; i < 10000; i++) {
flag++;
}
System.out.println(flag);
System.out.println("線程1結束");
}
}).start();
new Thread(new Runnable() {
@Override
public void run() {
for (int i = 0; i < 10000; i++) {
flag++;
}
System.out.println(flag);
System.out.println("線程2結束");
}
}).start();
}
運行以上代碼發現:運行完程序後我們的理論值應該需要flag = 20000,而實際得到的值大部分都不是我們的期望值。
由此可見,當flag值改變時,flag值會重新寫入主內存區,當需要操作時再從主內存讀取值到高速緩存區。
下面先來看線程內存交互圖:
Java內存模型定義了八種操作:
lock(鎖定):作用於主內存的變量,它把一個變量標識爲一個線程獨佔的狀態;
unlock(解鎖):作用於主內存的變量,它把一個處於鎖定狀態的變量釋放出來,釋放後的變量纔可以被其他線程鎖定;
read(讀取):作用於主內存的變量,它把一個變量的值從主內存傳送到線程中的工作內存,以便隨後的load動作使用;
load(載入):作用於工作內存的變量,它把read操作從主內存中得到的變量值放入工作內存的變量副本中;
use(使用):作用於工作內存的變量,它把工作內存中一個變量的值傳遞給執行引擎;
assign(賦值):作用於工作內存的變量,它把一個從執行引擎接收到的值賦值給工作內存中的變量;
store(存儲):作用於工作內存的變量,它把工作內存中的一個變量的值傳送到主內存中,以便隨後的write操作;
write(寫入):作用於主內存的變量,它把store操作從工作內存中得到的變量的值寫入主內存的變量中。
針對第一部分代碼:
對於線程一來說,當線程二將最新的值變量值write到主內存中時,線程一是沒有感知的,也就是說線程一併不知道何時read內存。
那如何能通知到線程一呢?這是否需要關鍵字Volatile?
針對第二部分代碼:
對於任何一個線程來說例如當線程一將flag=2的值 write到主內存時,此時線程二可能正要將flag=2 write 到主內存中。那對於flag來說其實已經累加過一次應該是3,所以就會出現併發問題使得最後的值不等於20000.那如果我們對flag的內存加鎖,同時只允許一個線程對其操作會如何呢?這是否需要關鍵字synchronized?