Java 内存模型

tech2025-07-17  3

一、Java 内存模型的基础

1 . 并发编程模型的两个关键问题

通信 :是指线程之间以何种机制来交换信息,通信机制为共享内存 和 消息传递; 同步 :是指程序中用于控制不同线程间操作发生相对顺序的机制;

2 . Java 内存模型的抽象结构

Java 中实例域,静态域和数组元素都存储在堆内存中,堆内存在线程之间共享,而局部变量,方法参数和异常处理器参数不会在线程之间共享

JMM 定义了线程和主内存之间的抽象关系:线程之间的共享变量存储在主内存中,每个线程都有一个私有的本地内存,本地内存中存储了该线程以读写共享变量的副本。

线程A 和线程B 之间通信的步骤 :

线程 A 把本地内存 A 中更新锅的共享变量刷新到主内存中去;线程 B 到主内存中去读取线程 A 之前已更新过的共享变量;
3 . happens-before 简介

在 JMM 中如果一个操作执行的结果需要对另外一个操作可见,那么这两个操作之间必须要存在 happens-before 关系,两个操作即可以在一个线程之内,也可以在不同线程之间

规则如下 :

程序顺序规则 :一个线程中的每个操作,happens-before 于该线程中的任意后续操作;监视器锁规则 :对一个锁的解锁,happens-before 于随后对这个锁的加锁;volatile 变量规则 :对于一个volatile 域的写, happens-before 于任意后续对这个 volatile域的读;传递性 :如果 A happens-before B,而且 B happens-before C ,那么 A happens-before C;

二、volatile 的内存语义

1 . volatile 的特性
可见性 :对一个volatile 变量的读,总能看到(任意线程)对这个volatile 变量最后的写入; 原子性 :对任意单个 volatile 变量的读/些具有原子性,但是类似volatile ++这种复合操作不具有原子性;
2 . volatile 写-读建立的happens-before 关系

假设线程 A 执行 writer() 方法之后,线程 B 执行 reader() 方法:

根据程序次序规则: 1 happens-before 2;3 happens-before 4根据 volatile 规则:2 happens-before 3根据 happens-before 的传递性规则,1 happens-before 4
3 . volatoile 写-读的内存语义

volatile 写的内存语义: 当写一个 volatile 变量时,JMM 会把该线程对相应的本地内存中; volatile 读的内存语义 :当读一个volatile 变量时,JMM会把该线程对应的本地内存设为无效,线程接下来从主存中读取共享变量;

4 . voaltile 内存语义的实现

为了实现 volatile 内存语义,JMM 会限制编译器重排序 和 处理器重排序

为了实现 volatile 内存语义,编译器在生成字节码时,会在指令序列插入内存屏障来禁止特定类型的处理器重排序;

JMM内存屏障插入策略 :

在每个 volatile 写操作的前面插入 StoreStore 屏障;在每个 volatile 写操作的后面插入 StoreLoad 屏障;在每个 volatile 读操作的后面插入 LoadLoad 屏障;在每个 volatile 读操作的后面插入 LoadStore 屏障;

三、锁的内存语义

1 . 锁的释放 - 获取建立的 happens - before 关系

锁除了让临界区互斥执行,还可以让释放锁的线程向获取同一个锁的线程发消息;

happens-before 关系图 :

2 . 锁的释放和获取的内存语义

锁释放的内存语义 :当线程释放锁时,JMM 会把该线程对应的本地内存中的共享变量刷新到主内存中; 锁获取的内存语义 :当线程获取锁时。JMM 会把该线程线程对应的本地内存置为无效,从而使得被监视器保护的临界区代码必须从主内存中读取共享变量;

锁释放与 volatile 写有着相同的内存语义;锁获取与 volatile 读有相同的内存语义;

3 . 锁内存语义的实现

ReentrantLock 分为公平锁 和 非公平锁;

公平锁 : 加锁方法 lock () 调用轨迹如下:

ReentrantLock : lock();FariSync : lock() ;AbstractQueuedSynchronizer : acquire ( int arg );ReentrantLock : tryAcqurie ( int acquires) ;

解锁方法 unlock () 调用轨迹如下:

ReentrantLock : unlock () ;AbstractQueuedSynchronizer : release ( int arg );Sync : tryRerlase ( int releases) ;

非公平锁 : 加锁方法**lock ()**调用轨迹如下:

ReentantLock : lock () :NonfairSync : Lock () ;AbstractQueuedSynchronizer : compareAndSetState ( int expect, int update);

解锁方法 unlock () 调用轨迹如下:

ReentrantLock : unlock () ;AbstractQueuedSynchronizer : release ( int arg );Sync : tryRerlase ( int releases) ;

四、final 域的内存语义

1. final 域的重排序规则
在构造函数内对一个final 域的写入,与随后把这个构造对象的引用赋值给一个引用变量,这两个操作之间不能重排序;初次读一个包含final 域的对象的引用,与随后初次读这个 final 域,这两个操作之间不能重排序;
2. 写 final 域的重排序规则
JMM 禁止编译器把 final 域的写重排序到构造函数之外;编译器会在 final 域的写之后,构造函数 return 之前,插入一个 Store Store 屏障,这个屏障禁止处理器把 final 域写重排序到构造函数之外;

写 final 域的重排序规则可以确保:在对象引用为任意线程可见之前,对象的final 域已经被正确初始化过了,而普通域不具有这个保障;

3. 读 final 域的重排序规则

在一个线程中,初次读对象引用与初次读该对象包含的 final 域,JMM禁止处理器重排序这两个操作,编译器会在读 final 域操作之前插入一个 LoadLoad 屏障;

读 final 域的重排序规则可以确保 :在读一个对象的 final 域之前,一定会先读包含这个 final 域的对象的引用;

五、happens-before

1. happens-before定义
如果一个操作 happens-before 另一个操作,那么第一个操作的执行结果将对第二个操作可见,而且第一个操作的执行顺序排在第二个操作的之前;两个操作之间存在 happens-before 关系,并不意味着 Java 平台的具体实现必须要按照 happens-before 关系指定的顺序来执行,如果重排序之后的执行结果;
2. happens-before 规则
程序顺序规则:一个线程中的每个操作,happens-before 与该线程中任意后续操作;监视器锁规则:对一个锁的解锁,happens-before 于随后对这个锁的加锁;voaltile 变量规则:对一个volatile 域的写,happens-before 于任意后续对这个 volatile 域的读;传递性:如果 A happens-before B,且 B happens-before C 那么 A happens-before C;start () 规则:如果线程 A 执行操作 ThreadB.start() ,那么 A 线程的 ThreadB.start() 操作 happens-before 于线程 B 中的任意操作;join () 规则:如果线程 A 执行操作 TreadB.join() 并成功返回,那么线程 B 中的任意操作 happens-before 于线程 A 从 ThreadB.join() 操作成功返回;
最新回复(0)