文章目录
- 1、Java内存模型
- 2、JMM的核心概念
- 1)主内存与工作内存
- 2)内存可见性
- 3)JMM的三大特性:原子性、可见性、有序性。
- 3、JMM中的八种操作
- 4、Happens-before 规则
- 5、样例:
1、Java内存模型
Java内存模型
(Java Memory Model,JMM)是一种抽象的概念,用于定义多线程程序如何与内存进行交互。JMM并不真实存在,它是一种规范
,规定了程序中变量在内存中的访问方式。
计算机在执行程序时,每条指令都是在CPU中执行的。而执行指令的过程中,势必涉及到数据的读取和写入。由于程序运行过程中的临时数据是存放在主存(物理内存)当中的,这时就存在一个问题,由于CPU执行速度很快,而从内存读取数据和向内存写入数据的过程,跟CPU执行指令的速度比起来要慢的多(硬盘 < 内存 <缓存cache < CPU)。因此如果任何时候对数据的操作都要通过和内存的交互来进行,会大大降低指令执行的速度。因此在CPU里面就有了高速缓存。也就是当程序在运行过程中,会将运算需要的数据从主存复制一份到CPU的高速缓存当中,那么CPU进行计算时,就可以直接从它的高速缓存中读取数据或向其写入数据了。当运算结束之后,再将高速缓存中的数据刷新到主存当中。
2、JMM的核心概念
1)主内存与工作内存
- 主内存:所有变量都存储在主内存中,主内存是共享的。
- 工作内存:每个线程都有自己的工作内存,工作内存中保存了主内存中变量的副本。线程对变量的所有操作(读取、写入)都在工作内存中进行,最后再将结果同步回主内存。
2)内存可见性
- JMM规定了线程对变量的读取和写入顺序,确保变量的修改在其他线程中可见。即,当一个线程修改了变量的值,其他线程最终能看到这个修改后的值。
3)JMM的三大特性:原子性、可见性、有序性。
-
原子性
一个或多个操作,要么全部执行,要么全部不执行(执行的过程中是不会被任何因素打断的)。 -
可见性
一个线程对共享变量的修改,能够被其他线程看到。通过 volatile 关键字、锁(如 synchronized)来实现可见性。 -
程序的执行在实际运行时可能会被重排序,但JMM提供了一定的保证,使得某些操作在多线程环境中会按照程序的顺序执行。
3、JMM中的八种操作
JMM抽象了线程和主内存之间的关系,定义了以下八种操作来完成主内存和工作内存之间的交互:
• lock(锁定):作用于主内存的变量,把一个变量标识为一条线程独占状态。
• unlock(解锁):作用于主内存变量,把一个处于锁定状态的变量释放出来,释放后的变量才可以被其他线程锁定。
• read(读取):作用于主内存变量,把一个变量值从主内存传输到线程的工作内存中,以便随后的load动作使用。
• load(载入):作用于工作内存的变量,它把read操作从主内存中得到的变量值放入工作内存的变量副本中。
• use(使用):作用于工作内存的变量,把工作内存中的一个变量值传递给执行引擎,每当虚拟机遇到一个需要使用变量的值的字节码指令时将会执行这个操作。
• assign(赋值):作用于工作内存的变量,它把一个从执行引擎接收到的值赋值给工作内存的变量,每当虚拟机遇到一个给变量赋值的字节码指令时执行这个操作。
• store(存储):作用于工作内存的变量,把工作内存中的一个变量的值传送到主内存中,以便随后的write的操作。
• write(写入):作用于主内存的变量,它把store操作从工作内存中一个变量的值传送到主内存的变量中。
4、Happens-before 规则
JMM还定义了一个重要的概念:happens-before,它是判断数据是否存在竞争、线程是否安全的依据。happens-before原则规定了以下几种情况:
• 程序次序规则:一个线程中的每个操作,happens-before于该线程中的任意后续操作。
• 监视器锁规则:对一个锁的解锁,happens-before于随后对这个锁的加锁。
• volatile变量规则:对一个volatile域的写,happens-before于任意后续对这个volatile域的读。
• 传递性:如果A happens-before B,且B happens-before C,那么A happens-before C。
• start()规则:如果线程A执行操作ThreadB.start()(启动线程B),那么A线程的ThreadB.start()操作happens-before于线程B中的任意操作。
• join()规则:如果线程A执行操作ThreadB.join()并成功返回,那么线程B中的任意操作happens-before于线程A从ThreadB.join()操作成功返回。
• 中断规则:对线程interrupt()方法的调用happens-before于被中断线程的代码检测到中断事件的发生。
• 终结器规则:对象的构造函数执行、结束happens-before于它的finalize()方法的开始。
JMM定义了一组 happens-before 规则,用来确定操作之间的顺序,确保内存可见性和有序性
5、样例:
public class VolatileExample {
private volatile boolean flag = false;
public void writer() {
flag = true; // 写操作,保证对其他线程可见
}
public void reader() {
if (flag) { // 读操作,确保读取到最新值
System.out.println("Flag is true");
}
}
}
public class SynchronizedExample {
private int count = 0;
public synchronized void increment() {
count++; // synchronized 确保可见性和原子性
}
public synchronized int getCount() {
return count; // synchronized 确保读取最新值
}
}
在 VolatileExample 中,flag 变量使用 volatile 修饰,确保在一个线程修改 flag 后,其他线程能够立即看到变化。在 SynchronizedExample 中,increment 和 getCount 方法使用 synchronized 修饰,确保操作的可见性和原子性。