Java內(nèi)存模型(JMM)開(kāi)發(fā)應(yīng)用
JMM核心內(nèi)容概覽與重要程度評(píng)級(jí)
在學(xué)習(xí)JMM前,我們先了解其核心內(nèi)容體系及重要程度:
| 內(nèi)容模塊 | 重要程度 | 說(shuō)明 |
|---|---|---|
| 1. JMM基礎(chǔ)概念 | ???? | 理解JMM的出發(fā)點(diǎn)和基本架構(gòu) |
| - 硬件基礎(chǔ)與并發(fā)挑戰(zhàn) | ???? | 了解JMM存在的必要性 |
| - 主內(nèi)存與工作內(nèi)存 | ???? | JMM的核心抽象概念 |
| 2. 內(nèi)存間交互操作 | ??? | JMM的基礎(chǔ)操作定義 |
| 3. volatile關(guān)鍵字 | ????? | 最常用的同步機(jī)制,必須深入掌握 |
| 4. synchronized內(nèi)存語(yǔ)義 | ????? | 理解鎖的內(nèi)存效應(yīng) |
| 5. happens-before規(guī)則 | ????? | JMM的理論核心,解決可見(jiàn)性問(wèn)題的關(guān)鍵 |
| 6. 原子性、可見(jiàn)性、有序性 | ????? | 并發(fā)編程的三大核心問(wèn)題 |
| 7. 安全發(fā)布模式 | ???? | 實(shí)際開(kāi)發(fā)中的常用技巧 |
| 8. final字段語(yǔ)義 | ??? | 特殊但重要的內(nèi)存語(yǔ)義 |
| 9. 雙重檢查鎖定問(wèn)題 | ???? | 經(jīng)典問(wèn)題的分析與解決方案 |
| 10. JMM底層實(shí)現(xiàn) | ??? | 理解原理,優(yōu)化性能 |
接下來(lái),我們將按照重要程度,逐一深入講解各個(gè)模塊。
1. JMM是什么?為什么需要JMM?
1.1 JMM的定義與作用
Java內(nèi)存模型(Java Memory Model, JMM) 是Java虛擬機(jī)規(guī)范中定義的一種抽象規(guī)范,用于屏蔽各種硬件和操作系統(tǒng)的內(nèi)存訪問(wèn)差異,實(shí)現(xiàn)Java程序在各種平臺(tái)下都能達(dá)到一致的內(nèi)存訪問(wèn)效果。
JMM的核心作用:
- 定義規(guī)則:規(guī)定多線程環(huán)境下變量的訪問(wèn)方式
- 提供保證:確保在不同平臺(tái)上內(nèi)存訪問(wèn)行為的一致性
- 允許優(yōu)化:在保證正確性的前提下允許編譯器和處理器進(jìn)行優(yōu)化
1.2 為什么需要JMM:硬件層面的挑戰(zhàn)
現(xiàn)代計(jì)算機(jī)系統(tǒng)的多層次存儲(chǔ)架構(gòu)導(dǎo)致了并發(fā)編程的三大核心問(wèn)題:
public class ConcurrencyProblems {
private static boolean ready = false;
private static int number = 0;
public static void main(String[] args) {
// 線程1:數(shù)據(jù)準(zhǔn)備
Thread writer = new Thread(() -> {
number = 42; // 操作1:可能被重排序到操作2之后
ready = true; // 操作2:可能先執(zhí)行
});
// 線程2:數(shù)據(jù)處理
Thread reader = new Thread(() -> {
while (!ready) {
// 等待ready變?yōu)閠rue
Thread.yield();
}
// 可能輸出0而不是42!
System.out.println("Number: " + number);
});
writer.start();
reader.start();
}
}問(wèn)題根源:
- CPU緩存一致性:多核CPU各有緩存,數(shù)據(jù)更新不同步
- 指令重排序:編譯器和處理器為優(yōu)化性能重新排序指令
- 內(nèi)存可見(jiàn)性:一個(gè)線程的修改對(duì)其他線程不可見(jiàn)
2. JMM的核心架構(gòu):主內(nèi)存與工作內(nèi)存
JMM通過(guò)抽象的內(nèi)存模型解決上述問(wèn)題:
工作內(nèi)存與主內(nèi)存的交互通過(guò)8種原子操作完成:
public class MemoryOperations {
private int sharedValue = 0;
public void operationExample() {
// 1. read: 從主內(nèi)存讀取變量到傳輸通道
// 2. load: 將read得到的值放入工作內(nèi)存的變量副本
// 相當(dāng)于: int temp = sharedValue; (但這是高級(jí)語(yǔ)言表示)
// 3. use: 將工作內(nèi)存中的變量傳遞給執(zhí)行引擎
int result = sharedValue * 2;
// 4. assign: 將執(zhí)行引擎的結(jié)果賦給工作內(nèi)存中的變量
sharedValue = result + 1;
// 5. store: 將工作內(nèi)存中的變量值傳輸?shù)街鲀?nèi)存的傳輸通道
// 6. write: 將store獲取的值放入主內(nèi)存的變量
// 7. lock: 將主內(nèi)存變量標(biāo)記為線程獨(dú)占狀態(tài)
// 8. unlock: 釋放鎖定的變量
}
}3. ????? volatile關(guān)鍵字深度解析
3.1 volatile的語(yǔ)義與保證
volatile是JVM提供的最輕量級(jí)的同步機(jī)制,提供兩大保證:
- 可見(jiàn)性保證:對(duì)volatile變量的寫(xiě)操作立即對(duì)其他線程可見(jiàn)
- 禁止重排序:阻止編譯器和處理器對(duì)volatile操作進(jìn)行重排序
public class VolatileExample {
private volatile boolean flag = false;
private int value = 0;
public void writer() {
value = 42; // 普通寫(xiě)操作
// StoreStore內(nèi)存屏障:禁止上面的普通寫(xiě)與下面的volatile寫(xiě)重排序
flag = true; // volatile寫(xiě)操作
// StoreLoad內(nèi)存屏障:確保volatile寫(xiě)立即對(duì)其他處理器可見(jiàn)
}
public void reader() {
// LoadLoad內(nèi)存屏障:確保volatile讀之前的所有讀操作已完成
if (flag) { // volatile讀操作
// LoadStore內(nèi)存屏障:確保volatile讀之后的寫(xiě)操作不會(huì)重排序到讀之前
System.out.println(value); // 保證看到value = 42
}
}
}3.2 volatile的實(shí)現(xiàn)原理
在硬件層面,volatile通過(guò)內(nèi)存屏障指令實(shí)現(xiàn):
public class VolatileBarrier {
private volatile int value;
public void setValue(int newValue) {
this.value = newValue;
// 對(duì)應(yīng)x86匯編代碼:
// mov %eax,0x10(%rsi) ; 將newValue存入value的內(nèi)存地址
// lock addl $0x0,(%rsp) ; StoreLoad內(nèi)存屏障(mfence指令)
}
public int getValue() {
// volatile讀在x86上不需要特殊指令
// 因?yàn)閤86的內(nèi)存模型已經(jīng)保證了可見(jiàn)性(TSO模型)
return value;
}
}內(nèi)存屏障類型:
- LoadLoad屏障:禁止讀操作重排序
- StoreStore屏障:禁止寫(xiě)操作重排序
- LoadStore屏障:禁止讀與寫(xiě)操作重排序
- StoreLoad屏障:禁止寫(xiě)與讀操作重排序(最重量級(jí))
3.3 volatile的使用場(chǎng)景與限制
適用場(chǎng)景:
- 狀態(tài)標(biāo)志位
- 一次性安全發(fā)布
- 獨(dú)立觀察(independent observation)
- 開(kāi)銷較低的讀-寫(xiě)鎖策略
不適用場(chǎng)景:
- 復(fù)合操作(如i++)
- 依賴于當(dāng)前值的操作(如value = value + 1)
public class VolatileUsage {
// 場(chǎng)景1:狀態(tài)標(biāo)志位
private volatile boolean shutdownRequested;
public void shutdown() {
shutdownRequested = true;
}
public void doWork() {
while (!shutdownRequested) {
// 執(zhí)行工作任務(wù)
}
}
// 場(chǎng)景2:一次性安全發(fā)布
private volatile Resource resource;
public Resource getResource() {
if (resource == null) {
synchronized(this) {
if (resource == null) {
resource = new Resource(); // 安全發(fā)布
}
}
}
return resource;
}
}4. ????? synchronized的內(nèi)存語(yǔ)義
synchronized不僅提供互斥執(zhí)行,還提供重要的內(nèi)存語(yǔ)義:
public class SynchronizedMemory {
private int counter = 0;
private final Object lock = new Object();
public void increment() {
synchronized(lock) {
// monitorenter指令:
// 1. 清空工作內(nèi)存
// 2. 從主內(nèi)存重新加載所有共享變量
counter++;
// 臨界區(qū)內(nèi)的操作不會(huì)被重排序到臨界區(qū)外
}
// monitorexit指令:
// 1. 將工作內(nèi)存中的修改刷新到主內(nèi)存
// 2. 釋放鎖
}
public int getCounter() {
synchronized(lock) {
// 獲取鎖會(huì)強(qiáng)制從主內(nèi)存重新讀取變量
return counter;
}
}
}synchronized的內(nèi)存語(yǔ)義:
- 進(jìn)入同步塊:清空工作內(nèi)存,從主內(nèi)存重新加載變量
- 退出同步塊:將工作內(nèi)存中的修改刷新到主內(nèi)存
- 互斥執(zhí)行:確保同一時(shí)刻只有一個(gè)線程執(zhí)行臨界區(qū)代碼
5. ????? happens-before規(guī)則
happens-before是JMM的理論核心,定義了操作之間的可見(jiàn)性關(guān)系。
5.1 happens-before規(guī)則詳解
public class HappensBeforeExample {
private int x = 0;
private volatile boolean v = false;
private int y = 0;
private final Object lock = new Object();
public void demo() {
// 規(guī)則1:程序次序規(guī)則
x = 1; // 操作A
v = true; // 操作B:A happens-before B
// 規(guī)則2:volatile變量規(guī)則
if (v) { // 操作C:B happens-before C
y = x; // 操作D:C happens-before D
}
// 規(guī)則3:傳遞性規(guī)則
// A happens-before B, B happens-before C, C happens-before D
// 因此 A happens-before D
// 規(guī)則4:管程鎖定規(guī)則
synchronized(lock) { // 加鎖E
x = 2; // 操作F:E happens-before F
} // 解鎖G:F happens-before G
// 規(guī)則5:線程啟動(dòng)規(guī)則
Thread t = new Thread(() -> {
System.out.println(x); // 看到x=2
});
t.start(); // start() happens-before 線程中的所有操作
// 規(guī)則6:線程終止規(guī)則
try {
t.join(); // 線程中的所有操作 happens-before join()返回
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
// 規(guī)則7:對(duì)象終結(jié)規(guī)則
// 對(duì)象的構(gòu)造函數(shù)執(zhí)行結(jié)束 happens-before finalize()方法開(kāi)始
}
}5.2 happens-before的數(shù)學(xué)基礎(chǔ)
happens-before關(guān)系是一個(gè)偏序關(guān)系,具有:
- 自反性:A happens-before A
- 反對(duì)稱性:如果A happens-before B且B happens-before A,則A=B
- 傳遞性:如果A happens-before B且B happens-before C,則A happens-before C
6. ????? 原子性、可見(jiàn)性、有序性
這是并發(fā)編程的三大核心問(wèn)題,JMM為每個(gè)問(wèn)題提供了解決方案。
6.1 原子性(Atomicity)
原子性是指一個(gè)操作不可中斷,要么全部執(zhí)行成功,要么完全不執(zhí)行。
public class AtomicityExample {
private int basicType = 0; // 基本類型訪問(wèn)是原子的
private long longValue = 0L; // long和double可能非原子(但現(xiàn)代JVM通常保證原子性)
private volatile boolean flag = false; // volatile保證單個(gè)讀/寫(xiě)的原子性
// 復(fù)合操作不是原子的
public void nonAtomicIncrement() {
basicType++; // 不是原子操作!分解為read-modify-write三步
}
// 保證原子性的方式
private final AtomicInteger atomicInt = new AtomicInteger(0);
private final Object lock = new Object();
private int synchronizedValue = 0;
public void atomicOperations() {
// 方式1:使用原子類
atomicInt.incrementAndGet(); // 原子操作
// 方式2:使用同步
synchronized(lock) {
synchronizedValue++; // 原子操作
}
// 方式3:使用volatile變量(僅適用于特定場(chǎng)景)
flag = true; // 原子操作
}
}6.2 可見(jiàn)性(Visibility)
可見(jiàn)性是指當(dāng)一個(gè)線程修改了共享變量的值,其他線程能夠立即得知這個(gè)修改。
public class VisibilityExample {
private int noVisibility = 0; // 無(wú)可見(jiàn)性保證
private volatile boolean hasVisibility = false; // volatile保證可見(jiàn)性
private int synchronizedValue = 0; // synchronized保證可見(jiàn)性
private final Object lock = new Object();
public void demonstrate() {
// 線程1:修改數(shù)據(jù)
new Thread(() -> {
noVisibility = 42;
hasVisibility = true;
synchronized(lock) {
synchronizedValue = 100;
}
}).start();
// 線程2:讀取數(shù)據(jù)
new Thread(() -> {
// 可能看不到noVisibility的更新
while (!hasVisibility) {
// 等待hasVisibility變?yōu)閠rue
}
// 保證看到noVisibility = 42(因?yàn)関olatile寫(xiě)happens-before volatile讀)
synchronized(lock) {
// 保證看到synchronizedValue = 100
}
}).start();
}
}6.3 有序性(Ordering)
有序性是指程序執(zhí)行的順序按照代碼的先后順序執(zhí)行。
public class OrderingExample {
private int x = 0;
private int y = 0;
private volatile boolean ready = false;
public void orderingDemo() {
// 線程1:可能被重排序
new Thread(() -> {
x = 1; // 操作1
y = 2; // 操作2
ready = true; // 操作3:volatile寫(xiě),阻止重排序
}).start();
// 線程2
new Thread(() -> {
while (!ready) {
// 等待
}
// 由于volatile的語(yǔ)義,這里保證看到x=1, y=2
// 不會(huì)出現(xiàn)y=2但x=0的情況
}).start();
}
}7. ???? 安全發(fā)布模式
安全地發(fā)布對(duì)象是并發(fā)編程中的常見(jiàn)需求,JMM提供了多種模式。
public class SafePublication {
// 方式1:靜態(tài)初始化器(最安全)
private static final Resource staticResource = new Resource();
// 方式2:volatile字段
private volatile Resource volatileResource;
public void initVolatileResource() {
volatileResource = new Resource(); // 安全發(fā)布
}
// 方式3:final字段
private final Resource finalResource;
public SafePublication() {
this.finalResource = new Resource(); // 安全發(fā)布
}
// 方式4:正常鎖保護(hù)
private Resource guardedResource;
private final Object lock = new Object();
public void initGuardedResource() {
synchronized(lock) {
if (guardedResource == null) {
guardedResource = new Resource(); // 安全發(fā)布
}
}
}
// 方式5:線程安全容器
private final Map<String, Resource> safeMap
= Collections.synchronizedMap(new HashMap<>());
private final ConcurrentMap<String, Resource> concurrentMap
= new ConcurrentHashMap<>();
public void addToSafeMap(String key) {
safeMap.put(key, new Resource()); // 安全發(fā)布
}
}8. ???? 雙重檢查鎖定(DCL)問(wèn)題與解決方案
雙重檢查鎖定是一個(gè)經(jīng)典的并發(fā)模式,但存在陷阱。
8.1 錯(cuò)誤的DCL實(shí)現(xiàn)
public class BrokenDCL {
private static Resource resource; // 沒(méi)有volatile!
public static Resource getInstance() {
if (resource == null) { // 第一次檢查(無(wú)鎖)
synchronized(BrokenDCL.class) { // 加鎖
if (resource == null) { // 第二次檢查(有鎖)
resource = new Resource(); // 問(wèn)題所在!
// 可能發(fā)生的重排序:
// 1. 分配內(nèi)存空間
// 2. 將引用指向內(nèi)存空間(此時(shí)resource != null)
// 3. 初始化對(duì)象(還未執(zhí)行)
// 其他線程可能拿到未完全初始化的對(duì)象!
}
}
}
return resource;
}
}8.2 正確的DCL實(shí)現(xiàn)
public class CorrectDCL {
// 使用volatile禁止重排序
private static volatile CorrectDCL instance;
private final int value;
private final String name;
private CorrectDCL() {
this.value = 42; // 初始化final字段
this.name = "DCL"; // 初始化普通字段
// 構(gòu)造函數(shù)執(zhí)行
}
public static CorrectDCL getInstance() {
if (instance == null) { // 第一次檢查(無(wú)鎖)
synchronized(CorrectDCL.class) { // 加鎖
if (instance == null) { // 第二次檢查(有鎖)
instance = new CorrectDCL(); // 安全發(fā)布
// volatile寫(xiě)插入內(nèi)存屏障,確保:
// 1. 所有初始化操作完成
// 2. 初始化結(jié)果對(duì)其他線程立即可見(jiàn)
}
}
}
return instance;
}
}9. ??? final字段的內(nèi)存語(yǔ)義
final字段在并發(fā)編程中有特殊的內(nèi)存語(yǔ)義,提供了安全初始化的保證。
public class FinalFieldExample {
private final int finalValue; // final字段
private int normalValue; // 普通字段
private volatile boolean ready = false;
public FinalFieldExample() {
normalValue = 1; // 普通字段寫(xiě)入(可能被重排序)
finalValue = 42; // final字段寫(xiě)入
// JMM在此隱式插入StoreStore內(nèi)存屏障
// 確保final字段的初始化不會(huì)被重排序到構(gòu)造函數(shù)之外
ready = true; // volatile寫(xiě)
}
public static void reader() {
FinalFieldExample obj = new FinalFieldExample();
// 保證看到finalValue的正確值(42)
// 即使沒(méi)有同步,也能看到正確初始化的final字段
int r1 = obj.finalValue;
// 可能看到normalValue的默認(rèn)值(0)而不是1
// 因?yàn)闆](méi)有同步保證
int r2 = obj.normalValue;
// 但如果通過(guò)volatile讀看到ready=true
// 那么也能保證看到所有字段的正確初始化值
if (obj.ready) {
// 保證看到finalValue=42和normalValue=1
}
}
}10. JMM在開(kāi)發(fā)中的實(shí)際應(yīng)用
10.1 性能優(yōu)化建議
- 減少同步范圍:只在必要時(shí)使用同步
- 使用volatile代替鎖:當(dāng)只需要可見(jiàn)性保證時(shí)
- 使用線程局部變量:避免共享,消除同步
- 使用并發(fā)容器:代替手動(dòng)同步的容器
10.2 常見(jiàn)陷阱與避免方法
public class CommonConcurrencyMistakes {
// 陷阱1:認(rèn)為volatile保證原子性
private volatile int count = 0;
public void unsafeIncrement() {
count++; // 不是原子操作!
}
// 解決方案:使用原子類或同步
private final AtomicInteger safeCount = new AtomicInteger(0);
private int synchronizedCount = 0;
private final Object lock = new Object();
public void safeIncrement() {
safeCount.incrementAndGet(); // 方式1:原子類
synchronized(lock) { // 方式2:同步
synchronizedCount++;
}
}
// 陷阱2:誤用雙重檢查鎖定
// 解決方案:使用volatile修飾實(shí)例變量
// 陷阱3:依賴線程優(yōu)先級(jí)
// 解決方案:不要依賴線程優(yōu)先級(jí)進(jìn)行正確性設(shè)計(jì)
// 陷阱4:在構(gòu)造函數(shù)中啟動(dòng)線程
public class ProblematicConstructor {
public ProblematicConstructor() {
new Thread(() -> {
// 可能訪問(wèn)未完全初始化的對(duì)象
}).start();
}
}
}總結(jié)
Java內(nèi)存模型是Java并發(fā)編程的基石,它通過(guò)定義一系列規(guī)則和happens-before關(guān)系,在多線程環(huán)境中提供了內(nèi)存可見(jiàn)性、原子性和有序性的保證。
關(guān)鍵要點(diǎn):
- 理解happens-before規(guī)則:這是理解線程間操作可見(jiàn)性的核心
- 正確使用volatile:了解其適用場(chǎng)景和限制
- 掌握安全發(fā)布模式:確保對(duì)象在線程間安全共享
- 避免常見(jiàn)陷阱:識(shí)別并避免常見(jiàn)的并發(fā)編程錯(cuò)誤
JMM既是一個(gè)規(guī)范也是一個(gè)工具,正確理解和使用JMM可以幫助我們編寫(xiě)出既正確又高效的多線程程序。在實(shí)際開(kāi)發(fā)中,應(yīng)該優(yōu)先使用java.util.concurrent包提供的高級(jí)并發(fā)工具,它們?cè)诖蠖鄶?shù)情況下都能提供更好的性能和更簡(jiǎn)單的編程模型。
到此這篇關(guān)于Java內(nèi)存模型(JMM)一文透徹理解的文章就介紹到這了,更多相關(guān)Java內(nèi)存模型JMM內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Java常用流程控制語(yǔ)句實(shí)現(xiàn)原理解析
這篇文章主要介紹了Java常用流程控制語(yǔ)句實(shí)現(xiàn)原理解析,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下2020-08-08
SpringBoot自定義maven-plugin插件整合asm代碼插樁
本文主要介紹了SpringBoot自定義maven-plugin插件整合asm代碼插樁,文中通過(guò)示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2022-02-02
java解析xml的4種方式的優(yōu)缺點(diǎn)對(duì)比及實(shí)現(xiàn)詳解
這篇文章主要介紹了java解析xml的4種方式的優(yōu)缺點(diǎn)對(duì)比及實(shí)現(xiàn)詳解,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下2019-07-07
java8 List<Object>去掉重復(fù)對(duì)象的幾種方法
本文主要介紹了java8 List<Object>去掉重復(fù)對(duì)象的幾種方法,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2022-04-04
SpringBoot實(shí)現(xiàn)評(píng)論回復(fù)功能(數(shù)據(jù)庫(kù)設(shè)計(jì))
這篇文章主要介紹了SpringBoot實(shí)現(xiàn)評(píng)論回復(fù)功能(數(shù)據(jù)庫(kù)設(shè)計(jì)),本文給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2021-04-04

