了解Java内存模型(Java Memory Model, JMM)
Java内存模型(Java Memory Model, JMM)是Java语言规范中规定的一组规则,定义了多线程程序中变量(包括实例字段、静态字段和数组元素)的访问方式。JMM的设计目标是保证线程之间的内存可见性和操作的有序性,从而帮助开发者编写并发安全的程序。本文将详细介绍Java内存模型的各个方面。
1. 内存模型的基础
JMM定义了线程和主内存之间的抽象关系。在JMM中,每个线程都有一个私有的本地内存,包含了该线程使用到的变量的副本。线程之间的通信(例如一个线程写入一个变量,另一个线程读取这个变量)必须通过主内存来完成。一个线程对变量的修改必须在写入主内存后,其他线程才能看到这次修改。
2. 内存可见性
内存可见性是指一个线程对变量的修改,另一个线程能够看到的条件。JMM通过以下几个关键字来保证内存可见性:
2.1 volatile关键字
volatile
关键字保证了变量在多个线程之间的可见性。当一个变量被声明为volatile时,JMM会保证所有线程都能看到这个变量的最新值。具体来说,JMM通过以下两个规则实现volatile的语义:
- 当写入一个volatile变量时,JMM会强制将该变量的值刷新到主内存中。
- 当读取一个volatile变量时,JMM会强制从主内存中读取该变量的最新值。
2.2 synchronized关键字
synchronized
关键字用于实现线程之间的互斥访问。除了互斥功能外,synchronized还可以保证内存可见性:
- 当一个线程进入synchronized块时,它必须先获得该块对象的锁,并清空本地内存中的变量副本。
- 当一个线程退出synchronized块时,它必须将对变量的修改刷新到主内存中,并释放锁。
通过这种机制,synchronized保证了在synchronized块内对变量的修改对其他线程可见。
3. 有序性
有序性是指在多线程程序中,程序的执行顺序与代码的顺序一致。JMM通过以下几个方面来保证有序性:
3.1 happens-before规则
JMM通过happens-before原则来定义操作之间的有序性。happens-before是指如果一个操作happens-before另一个操作,那么第一个操作的结果对第二个操作可见,并且第一个操作在第二个操作之前执行。JMM定义了以下几种常见的happens-before关系:
- 程序次序规则:在一个线程内,按照代码顺序,前面的操作happens-before后面的操作。
- 锁定规则:一个unlock操作happens-before后面对同一个锁的lock操作。
- volatile变量规则:对一个volatile变量的写操作happens-before后面对该变量的读操作。
- 线程启动规则:Thread对象的start()方法happens-before该线程的每一个动作。
- 线程终止规则:线程中的所有操作happens-before其他线程检测到该线程已经终止(通过Thread.join()或Thread.isAlive()方法)。
3.2 指令重排序
为了提高性能,编译器和处理器可能会对指令进行重排序,但这种重排序不会违反happens-before规则。JMM通过内存屏障(Memory Barriers)来禁止特定的重排序,从而保证程序的有序性和正确性。
4. 常见的内存一致性错误
在多线程编程中,未能正确处理内存可见性和有序性可能会导致一些常见的内存一致性错误,例如:
- 读脏数据:一个线程读取了另一个线程未同步的写入数据。
- 重排序导致的数据丢失:指令重排序导致程序的执行结果与预期不一致。
为了避免这些问题,开发者应当遵循JMM规定的规则,正确使用volatile和synchronized等关键字。
参考链接
- Java内存模型(JMM)详解:https://docs.oracle.com/javase/specs/jls/se11/html/jls-17.html
- Java并发编程实践(Java Concurrency in Practice):https://jcip.net/