共计 2900 个字符,预计需要花费 8 分钟才能阅读完成。
简介
Java 内存模型的次要目标是定义程序中各种变量的拜访规定,即关注在虚拟机中把变量的值写入内存和从内存中取出的底层细节。这里的变量指的是实例字段、动态字段、形成数组的元素等可被共享的变量。
主内存与工作内存
Java 内存模型规定所有的变量都存储在主内存(Main Memory)中,每个线程领有本人的工作内存(Working Memory)。
线程的工作内存保留了被该线程应用的变量的主内存正本,线程对变量的读取和赋值操作只能在本人的工作内存中进行,而不能间接读写主内存中的数据。
不同的线程不能间接拜访对方的工作内存中的变量,线程间变量数据的传递必须通过主内存作为中介实现。线程、工作内存和主内存三者的交互关系如图:
内存间的交互
对于主内存与工作内存之间的交互协定,即一个变量如何从主内存拷贝到工作内存、如何从工作内存同步回主内存的实现细节,Java 内存模型定义了八种操作,这八种操作每个都是原子的,不可拆分的,具体如下:
- lock(锁定):作用于主内存中的变量,它把一个变量标记为线程独占状态
- unlock(解锁):作用于主内存中的变量,它把一个标记为锁定状态的变量进行锁开释
- read(读取):作用于主内存中的变量,它把一个变量从主内存中传输到线程的工作内存,以便后续的 load 操作
- load(载入):作用于线程工作内存的变量,把 read 操作从主内存读取的变量加载到工作内存的变量正本中
- use(应用):作用于线程工作内存的变量,把工作内存中的变量的值传递给执行引擎,当虚拟机遇到一个须要应用变量的字节码指令时将会执行这个操作
- assign(赋值):作用于线程工作内存的变量,把一个从执行引擎承受的值赋值给工作内存中的变量,当线程遇到一个执行引擎须要赋值的字节码指令时将执行这个操作
- store(存储):作用于线程工作内存的变量,把一个工作内存中变量的值传递到主内存,以便前面的 write 操作来写入主内存
- write(写入):作用于主内存,把 store 过程传递过去的变量的值写入到主内存中
原子性、可见性与有序性
Java 内存模型的定义和规定都是围绕着在多线程并发执行过程中如何保障原子性、可见性和有序性这三个特色来建设的。
原子性(Atomicity)
Java 内存模型间接保障的原子性变量操作包含:read、load、assign、use、store、write 这六个,基于此,根本数据类型(char,short,int,boolean)拜访、读写都具备原子性(例外就是 long 和 double 的非原子性协定)。
如果利用场景须要一个更大范畴的原子性保障,Java 内存模型提供了 lock 和 unlock 的操作来满足这个需要,虚拟机虽未把 lock 和 unlock 间接放开给用户应用,然而提供了更高层次的字节码指令 monitorenter 和 monitorexit 来隐式的实现这两个操作,这两个字节码指令反映到 Java 中的实现就是同步块 synchronized 关键字。
可见性(Visibility)
可见性指的是线程对工作内存中共享变量的值的批改对其余线程立刻可见。Java 的内存模型通过在变量批改后把变量的值同步到主内存,在读取变量前从主内存中刷新变量的值到工作内存这种依赖来实现可见性,无论是一般变量还是 volatile 变量都是如此。
一般变量和 volatile 的区别在于,volatile 的非凡规定保障了新值能 立刻同步 到主内存,以及每次应用前都 强制 从主内存刷新,因而,volatile 能够保障多线程操作时变量的可见性,而一般变量不能保障这一点。
除了 volatile 以外,synchronized 和 final 也能够保障共享变量的可见性,其中 synchronized 的可见性是通过对一个变量执行 unlock 之前必须强制把变量的值同步到主内存实现的。final 关键字的可见性是指,在被 final 润饰的变量在结构器中一旦被初始化实现,并且结构器没有把“this”的援用传递进来(this 援用逃逸会导致其余线程有可能通过这个援用拜访到“初始化一半”的对象),那么其余线程中就能够看到 final 字段的值。
有序性
Java 虚拟机的即时编译器会对代码指令进行重排序优化,一般变量仅会保障在线程内代码执行过程中依赖这个变量后果的中央都能获取到正确的后果,然而不能保障变量赋值操作的程序和程序代码的程序是统一的。这种重排序优化在多线程操作下可能会导致意料不到的问题,用上面一段伪代码阐明下:
Map configOptions;
char[] configText;
// 此变量需申明为 volatile 变量
volatile boolean initialized = false;
// 上面代码在线程 A 中运行
// 模仿读取配置信息,当读取实现后 initialized 设置为 true
configOptions = new HashMap<>();
configText = readConfigFile(fileName);
processConfigOptions(configText, configOptions);
initialized = true;
// 上面代码在线程 B 中执行
while(!initialized) {sleep();
}
// 应用线程 A 中初始化好的配置信息
doSomethingWithConfig();
如果指令重排序,线程 A 中的 initialized = true 指令可能会在加载配置文件之前就被执行,那么线程 B 中会呈现应用未加载配置信息的谬误。
volatile 关键字能够禁用指令重排序,即不容许扭转 initialized = true 在代码指令中原有的地位,从而保障了代码依照预期的执行程序执行,确保不会呈现意料之外的问题。
除了 volatile 关键字之外,synchronized 也能够保障多线程环境下的有序性,synchronized 的特色保障了同一时刻只能有一个线程执行,多个线程是串行执行的,每一次 unlock 操作共享变量的批改必须强制刷新到主内存。
后行产生准则
Java 中只应用 volatile 和 synchronized 来保障有序性,那么在操作上就会比拟繁琐,Java 语言定义了“后行产生”(Happends-Before)准则,后行产生的操作后果对于前面的操作可见,这些准则无需任何同步器帮助人造存在,能够在代码中间接应用。具体的规定如下:
- 程序秩序规定:在一个线程内,依照控制流程序,书写在后面的操作后行产生与书序在前面的操作
- 管程锁定规定:一个 unlock 操作后行产生于后续对同一个锁的的 lock 操作
- volatile 变量规定:对 volatile 变量的批改操作后行于对该变量的读取操作
- 线程启动规定:Thread 对象的 start()办法后行产生于线程的每个操作
- 线程终止规定:线程中的所有操作后行产生于线程的终止操作
- 对象终止规定:一个对象的初始化操作(构造函数执行完结)后行产生于它的 finalize()办法的开始
- 传递性:如果 A 操作后行产生与 B 操作,B 操作后行产生与 C 操作,那么 A 操作后行产生于 C 操作
对于可见性的判断,咱们能够应用下面的规定间接进行判断,或者通过多个规定推导进行判断。