一篇文章深入聊聊Java的內(nèi)存模型
1、Java的內(nèi)存模型(JMM)介紹
JMM核心定義和作用
Java內(nèi)存模型(Java Memory Model,JMM)是Java虛擬機規(guī)范中定義的一種抽象概念,它規(guī)定了多線程環(huán)境下,線程如何與內(nèi)存進行交互。
JMM的核心作用:
定義程序中各個變量的訪問規(guī)則
確保多線程程序的可見性、有序性和原子性
屏蔽不同硬件平臺和操作系統(tǒng)的內(nèi)存訪問差異
JVM和JMM的區(qū)別
說到JMM,我們不得不提到它經(jīng)常被人所搞混淆的另一個概念JVM,我們用一張表來直觀表現(xiàn)出它們的區(qū)別。
JVM內(nèi)存結(jié)構(gòu) | Java內(nèi)存模型 | |
| 核心關(guān)注點 | 數(shù)據(jù)存儲的物理/邏輯分區(qū) | 線程與內(nèi)存的交互規(guī)則 |
| 內(nèi)容 | 堆、棧、方法區(qū)等內(nèi)存區(qū)域 | 主內(nèi)存、工作內(nèi)存抽象概念 |
| 目的 | 內(nèi)存分配與管理 | 多線程內(nèi)存可見性控制 |
2.JMM核心概念
主內(nèi)存和工作內(nèi)存
主內(nèi)存:所有線程共享的內(nèi)存區(qū)域,存儲所有實例字段、靜態(tài)字段和數(shù)組元素
工作內(nèi)存:每個線程私有的內(nèi)存空間,存儲線程使用變量的副本
當某個線程需要使用到內(nèi)存中的變量時,他會先從主內(nèi)存中復制一份該變量的副本到自己的工作內(nèi)存當中,使用完后再將該變量寫入主內(nèi)存的共享內(nèi)存中。

內(nèi)存間的交互操作
lock/unlock:作用于主內(nèi)存,標識變量為線程獨占狀態(tài)
read/load:從主內(nèi)存讀取變量到工作內(nèi)存
use/assign:工作內(nèi)存中使用和賦值操作
store/write:將工作內(nèi)存變量寫回主內(nèi)存
內(nèi)存三大特性
原子性
核心概念:原子性指一個操作或一系列操作要么全部執(zhí)行成功,要么全部不執(zhí)行,不會出現(xiàn)執(zhí)行到一半的狀態(tài)。
// 原子操作示例
int x = 10; // 原子的:一次性賦值
boolean flag = true; // 原子的
// 非原子操作示例
int i = 0;
i++; // 非原子的,實際包含3個步驟:
// 1.讀取i的值到寄存器
// 2.寄存器值加1
// 3.寫回內(nèi)存 可見性
核心概念:可見性指當一個線程修改了共享變量的值,其他線程能夠立即看到修改后的值。
private boolean running = true; // 主內(nèi)存中的變量
public void run() {
while (running) { // 工作內(nèi)存中的副本
// 看不到running被改為false
}
}
public void stop() {
running = false; // 修改主內(nèi)存,但工作內(nèi)存可能沒更新
}有序性
核心概念:有序性指程序執(zhí)行的順序按照代碼的先后順序執(zhí)行。但在多線程或優(yōu)化環(huán)境下,指令可能被重排序。
重排序原因:
編譯器優(yōu)化重排序
處理器指令級并行重排序
內(nèi)存系統(tǒng)重排序
private int x = 0;
private boolean flag = false;
// 線程1執(zhí)行
public void writer() {
x = 42; // 1
flag = true; // 2 可能被重排到1之前!
}
// 線程2執(zhí)行
public void reader() {
if (flag) { // 3
System.out.println(x); // 可能輸出0而不是42!
}
}3.Happens-Before規(guī)則
Happens-Before規(guī)則介紹
Happens-Before是JMM的核心概念,它定義了兩個操作之間的偏序關(guān)系:
如果操作A happens-before? 操作B
那么A的所有寫操作對B的讀操作都是可見的
有點難看懂,我們用一個簡單的例子就能快速理解
// 核心:happens-before ≠ 時間先后 int x = 0; int y = 0; // 時間上:先執(zhí)行1,后執(zhí)行2 x = 1; // 1 y = x + 1; // 2 // 邏輯上:1 happens-before 2 // 所以2一定能看到1寫入的值
A happens-before B 翻譯過來就是:A對B可見。
六大happens-before規(guī)則
- 程序次序規(guī)則:線程內(nèi),按照程序代碼順序,前面的操作happens-before后面的操作。
- 監(jiān)視器鎖規(guī)則:對一個鎖的解鎖操作happens-before后續(xù)對這個鎖的加鎖操作。
- volatile變量規(guī)則:對一個volatile變量的寫操作happens-before后續(xù)對這個變量的讀操作。
- 線程啟動規(guī)則:Thread對象的start()方法調(diào)用happens-before該線程的每一個動作。
- 線程終止規(guī)則:線程中的所有操作happens-before其他線程檢測到該線程已經(jīng)終止
- 傳遞性規(guī)則:如果A happens-before B,且B happens-before C,那么A happens-before C
4.volatile關(guān)鍵字
核心概述:volatile是一個重要的關(guān)鍵字,用于告知編譯器某個變量的值可能會被程序外部的因素意外修改,從而避免編譯器對該變量進行優(yōu)化。它的主要作用是確保每次訪問變量時都從內(nèi)存中讀取最新的值,而不是使用寄存器中的緩存值。
volatile提供了兩大保證:
- 可見性:修改立即對所有線程可見
- 有序性:禁止指令重排序
private volatile boolean flag = false;
private int count = 0;
public void writer() {
count = 42; // 普通寫操作
flag = true; // volatile寫操作
}
public void reader() {
if (flag) { // volatile讀操作
// 這里一定能看到count=42
System.out.println(count);
}
}5.JMM的常見誤區(qū)
volatile無法保證原子性
volatile可以保證可見性和有序性,但和synchronized不一樣,不能保證原子性
// 錯誤:以為volatile能保證原子性 volatile int count = 0; count++; // 非原子操作 // 正確:使用原子類或同步 AtomicInteger atomicCount = new AtomicInteger(0); atomicCount.incrementAndGet();
指令重排序的陷阱
// 可能由于重排序?qū)е聠栴}
int a = 0;
boolean flag = false;
// 線程1
a = 1; // 1
flag = true; // 2 可能重排到1之前
// 線程2
if (flag) {
System.out.println(a); // 可能輸出0
}此時我們需對flag使用volatile關(guān)鍵字修飾即可保證在a賦值后再執(zhí)行flag=true操作。
總結(jié)
到此這篇關(guān)于Java內(nèi)存模型的文章就介紹到這了,更多相關(guān)Java內(nèi)存模型內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Spring Security 將用戶數(shù)據(jù)存入數(shù)據(jù)庫
這篇文章主要介紹了Spring Security 如何將用戶數(shù)據(jù)存入數(shù)據(jù)庫,幫助大家更好的理解和學習Spring Security,感興趣的朋友可以了解下2020-09-09
詳解Java時區(qū)處理之Date,Calendar,TimeZone,SimpleDateFormat
這篇文章主要介紹了Java時區(qū)處理之Date,Calendar,TimeZone,SimpleDateFormat的區(qū)別于用法,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友們下面隨著小編來一起學習學習吧2020-07-07
Java Swing實現(xiàn)簡單的體重指數(shù)(BMI)計算器功能示例
這篇文章主要介紹了Java Swing實現(xiàn)簡單的體重指數(shù)(BMI)計算器功能,涉及Java Swing窗口組件布局、響應及數(shù)值運算相關(guān)操作技巧,需要的朋友可以參考下2017-12-12
SpringCloud中的Feign遠程調(diào)用最佳實踐方案
本文介紹了Feign作為聲明式HTTP客戶端的優(yōu)勢,包括提升代碼可讀性、簡化URL維護及支持自定義配置(日志級別、連接池),通過模塊化抽取Feign客戶端和公共類,實現(xiàn)代碼復用并解決掃描包問題,優(yōu)化微服務間調(diào)用效率,對SpringCloud Feign遠程調(diào)用相關(guān)知識感興趣的朋友一起看看吧2025-07-07

