Java 線程內(nèi)存模型
1. 前言
本節(jié)內(nèi)容是從操作系統(tǒng)的層面談并發(fā),本節(jié)課程我們需要掌握如下內(nèi)容:
- 了解 Java 的內(nèi)存模型定義,是 Java 并發(fā)編程基本原理的基礎(chǔ)知識(shí);
- 從概念上了解線程的私有內(nèi)存空間和主內(nèi)存,能夠從全局上了解線程是如何進(jìn)行內(nèi)存數(shù)據(jù)的存取操作的;
- 了解線程擁有私有空間的意義,私有空間能夠?yàn)榫€程提供獨(dú)有的數(shù)據(jù),其他線程不可干擾;
- 在多線程環(huán)境下,主內(nèi)存操作共享變量需要注意的事項(xiàng)需謹(jǐn)記,數(shù)據(jù)安全問(wèn)題很重要;
- Java 線程也是擁有生命周期的,了解它的生命周期,從宏觀上了解線程。
2. 什么是 Java 的內(nèi)存模型
定義: Java 內(nèi)存模型(即 Java Memory Model,簡(jiǎn)稱 JMM)本身是一種抽象的概念,并不真實(shí)存在,它描述的是一組規(guī)則或規(guī)范,通過(guò)這組規(guī)范定義了程序中各個(gè)變量(包括實(shí)例字段,靜態(tài)字段和構(gòu)成數(shù)組對(duì)象的元素)的訪問(wèn)方式。
3. Java 線程的私有內(nèi)存和主內(nèi)存
首先看下圖,圖中展示了Java 的內(nèi)存模型。
工作內(nèi)存(私有):由于JVM 運(yùn)行程序的實(shí)體是線程,而每個(gè)線程創(chuàng)建時(shí) JVM 都會(huì)為其創(chuàng)建一個(gè)工作內(nèi)存(棧空間),用于存儲(chǔ)線程私有的數(shù)據(jù)。線程私有的數(shù)據(jù)只能供自己使用,其他線程不能夠訪問(wèn)到當(dāng)前線程私有的內(nèi)存空間,保證了不同的線程在處理自己的數(shù)據(jù)時(shí),不受其他線程的影響。
主內(nèi)存(共享):Java 內(nèi)存模型中規(guī)定所有變量都存儲(chǔ)在主內(nèi)存,主內(nèi)存是共享內(nèi)存區(qū)域,所有線程都可以訪問(wèn)。從上圖中可以看到,Java 的并發(fā)內(nèi)存模型與操作系統(tǒng)的 CPU 運(yùn)行方式極其相似,這就是 Java 的并發(fā)編程模型。通過(guò)創(chuàng)建多條線程,并發(fā)的進(jìn)行操作,充分利用系統(tǒng)資源,達(dá)到高效的并發(fā)運(yùn)算。
4. 線程擁有私有空間的意義
我們知道,線程的私有空間中存儲(chǔ)的數(shù)據(jù),僅供當(dāng)前線程自己使用,其他線程不能夠?qū)?shù)據(jù)進(jìn)行訪問(wèn)。線程的私有空間會(huì)存放很多程序運(yùn)行時(shí)所必須的數(shù)據(jù),如:
程序計(jì)數(shù)器:記錄當(dāng)前方法執(zhí)行到了哪里,以便 CPU 切換回來(lái)之后能夠繼續(xù)執(zhí)行上次執(zhí)行到的位置,而不會(huì)進(jìn)行重復(fù)執(zhí)行或者遺漏。
局部變量:局部變量是方法中的變量,只供當(dāng)前方法使用。
方法參數(shù):Java 方法會(huì)定義自己的入?yún)?,入?yún)⒌恼鎸?shí)值也會(huì)記錄到內(nèi)存空間供當(dāng)前線程使用。
由于線程的內(nèi)存空間會(huì)存放很多數(shù)據(jù),這里只提以上三中數(shù)據(jù)以供同學(xué)理解線程私有空間的意義。
為了加深理解,我們一起看一個(gè)簡(jiǎn)單的代碼示例并進(jìn)行分析:
public class DemoTest{
public static void main(String[] args) {
sum(10); // 解析點(diǎn) 3
}
public static void sum(int num) {
int i = 5; // 解析點(diǎn) 1
set(); //解析點(diǎn) 2
System.out.println("num+i = "+ (num + i));
}
public static void set() {
int i = 100;
}
}
在給出結(jié)果之前,我們來(lái)分析下:
解析點(diǎn) 1 :設(shè)置 i 的值為 5;
解析點(diǎn) 2: 調(diào)用 set() 方法,邏輯如下。
public static void set() {
int i = 100;
}
那最終的結(jié)果是多少呢?
解析點(diǎn) 3:我們傳入的 sum 的參數(shù)值是 10,如果想確定結(jié)果,只要確定另外一個(gè)加數(shù) i 的值就行了。我們通過(guò)分析,在方法 sum(int num) 中的 int i = 5 與方法 set() 中的 int i = 100 是兩個(gè)不同的方法的局部變量,屬于線程私有的?;ハ嗖粫?huì)影響,所以set() 方法中的 int i = 100 不會(huì)影響最終的結(jié)果:
num+i = 15
5. 主內(nèi)存操作共享變量需要注意的事項(xiàng)
- 確定是否是多線程環(huán)境:多線程環(huán)境下操作共享變量需要考慮線程的安全性;
- 確定是否有增刪改操作:多線程環(huán)境下,如果對(duì)共享數(shù)據(jù)有增加,刪除或者修改的操作,需要謹(jǐn)慎。為了保證線程的同步性,必須對(duì)該共享數(shù)據(jù)進(jìn)行加鎖操作,保證多線程環(huán)境下,所有的線程能夠獲取到正確的數(shù)據(jù)。如生產(chǎn)者與消費(fèi)者模型,售票模型。這些會(huì)在后續(xù)章節(jié)進(jìn)行代碼實(shí)戰(zhàn)演練;
- 多線程下的讀操作:如果是只讀操作,對(duì)共享數(shù)據(jù)不需要進(jìn)行鎖操作,因?yàn)閿?shù)據(jù)本身未發(fā)生增刪改操作,不會(huì)影響獲取數(shù)據(jù)的準(zhǔn)確性。
6. Java 線程的生命周期
每個(gè)事物都有其生命周期,也就是事物從出生開(kāi)始到最終消亡這中間的整個(gè)過(guò)程。在其整個(gè)生命周期的歷程中,會(huì)有不同階段,每個(gè)階段對(duì)應(yīng)著一種狀態(tài),比如:人的一生會(huì)經(jīng)歷從嬰幼兒、青少年、青壯年、中老年到最終死亡,離開(kāi)這人世間,這是人一生的狀態(tài)。
同樣的,線程作為一種事物,也有生命周期,在其生命周期中也存在著不同的狀態(tài),不同的狀態(tài)之間還會(huì)有互相轉(zhuǎn)換。
Java 線程的聲明周期會(huì)經(jīng)歷 6 中不同的狀態(tài)變化,后續(xù)章節(jié)會(huì)有詳細(xì)描述。從線程的創(chuàng)建到線程執(zhí)行任務(wù)的完成,即 Java 線程的生命周期。
7. 小結(jié)
Java 并發(fā)理論基礎(chǔ)是基于Java 的內(nèi)存模型的,了解 Java 內(nèi)存模型,能夠更有助于后續(xù)對(duì)并發(fā)知識(shí)的理解和運(yùn)用了。Java 的內(nèi)存模型是并發(fā)原理的基礎(chǔ),在了解內(nèi)存模型的基礎(chǔ)上去理解共享內(nèi)存和私有內(nèi)存,了解不同內(nèi)存狀態(tài)以及 Java 線程的生命周期至關(guān)重要。