Java内存模型
Java内存模型的主要目标是定义程序中各个变量的访问规则,即JVM中将变量存储到内存中和从内存中取出变量这样的底层细节,变量包括了实例字段、静态字段和构成数组对象的元素,但是不包括局部变量和方法参数,内存模型规定了所有的变量都必须存储在主内存,线程对变量的所有操作(取值、赋值)都必须在内存中完成。
内存模型定义了8种操作来完成工作内存和主内存之间的实现细节,而且这几种操作都是原子的、不可再分的(64位的double和long类型除外),线程、工作内存和主内存之间交互如下图所示:
线程、工作内存与主内存交互图
lock(锁定):作用于主内存的变量,把一个变量标识为一条线程独占的状态。
unlock(解锁):作用于主内存的变量,将一个锁定状态的变量释放出来,释放的变量可由其他线程锁定。
read(读取):作用于主内存的变量,把一个变量从主内存传输到线程的工作内存中,以便随后的load使用。
load(载入):作用于工作内存的变量,把read操作从主内存中读取的变量放入工作内存的变量副本中。
use(使用):作用于工作内存的变量,把工作内存中的值传递给执行引擎,每当jvm遇到一个需要使用变量的值的字节码指令就执行这个操作。
assign(赋值):作用于工作内存的变量,把执行引擎接收到的值赋给工作内存中的变量,每当jvm遇到一个需要给变量赋值的字节码指令就执行这个操作。
store(存储):作用于工作内存的变量,把工作内存中的值传输到主内存中,供之后的write操作使用。
write(写入):作用于主内存的变量,把store操作传入的变量的值放入主内存中的变量中。
主内存->工作内存 read->load
工作内存->主内存 store->write
上述操作必须按顺序执行,但是并不一定需要连续执行。
针对以上操作需满足以下几个规则:
(1)不允许read,load,store和write单独出现
(2)不允许一个线程丢弃最近的assign操作
(3)一个新变量只能在内存中诞生
(4)一个变量在同一个时刻只允许一个线程进行lock操作
(5)不允许一个线程没有assign操作将数据从工作内存同步回主内存
(6)如果线程对一个变量进行lock操作,将会把工作内存中的数据清空。,需要重新load或assgin变量的值。
(7)对一个变量进行unlock操作之前,必须将数据从工作内存同步回主内存(执行store,write操作)
对volatile变量的特殊规则
JVM提供的最轻量级的同步机制,它具备以下2种特性:
(1)保证此变量对所有线程的可见性,但并不保证并发安全,java里面的运算并非原子操作,所以volatile修饰的变量在并发条件下不能保证线程安全。
底层原理:java的++操作,编译后由4条字节码指令(多条机器码指令)构成:
a.getstatic
b.iconst_1
c.iadd
d.putstatic
在执行iconst_1,iadd指令时,可能其他线程已经修改了操作栈顶的值,导致pustatic执行时,把已经过期的数据(比实际数据小)同步回主内存。
演示代码:
public class VolatileTest {
private static volatile int count = 0;
public static void main(String[] args) {
VolatileTest volatileTest = new VolatileTest();
for (int i = 0; i < 10000; i++) {
new Thread(new Runnable() {
@Override
public void run() {
count++;
}
}).start();
}
while (Thread.activeCount() > 2) {
Thread.yield();
}
System.out.println("累加结果:" + count);
}
}
image.png
反编译后的++操作字节码:
image.png
预期结果应该是10000,但是实际结果是9996,说明在累加过程中把过期的值同步回了主内存。
volatile的使用场景:
a.运算结果不依赖变量当前计算的值或者能够确保只有单一线程能够修改线程的值
b.变量不需要与其他状态变量共同参与不变约束(只需要一个变量就可以控制并发)
(2)禁止指令重排序优化
有volatile修饰的变量,赋值后多执行了一个"lock"操作,这个操作相当于一个内存屏障(指令重排序时不能将后面的指令重排序到内存屏障之前的位置),该指令使得本cpu的cache写入内存,同时会引起其他CPU或者别的内核无效化其cache,相当于做了"store和write操作",因而使得volatile修饰的变量的修改对其他CPU立即可见。
线程并发过程中的原子性、有序性和可见性
原子性:基本数据类型的访问读写是具备原子性的,原子性变量操作包括read、load、assign、use、store和write.
(1)synchroniozed关键字,底层由字节码指令monitorenter和monitorexit实现。
(2)lock和unlock操作
可见性:当一个线程修改了共享变量的值,其他线程能够立即得知这个修改。
有序性:在本线程中观察,所有操作都是有序的;如果在一个线程中观察另外一个线程,所有操作都是无序的。
Java与线程
(1)线程的实现
a.使用内核线程实现
直接由操作系统内核支持的线程,内核通过调度器对线程进行调度,将线程任务反映到各个处理器上.
b.使用用户线程实现
不是内核线程的线程
c.使用用户线程和轻量级进程实现
轻量级进程由内核线程支持,内核线程与轻量级进程是1:1的关系,进程与用户线程是1:N的关系,轻量级进程与用户线程是N:M的关系;SunJDK使用1:1的线程模型实现。
(2)Java线程调度
a.协同式线程调度
线程的执行时间由自己控制,线程把工作完成之后,要主动通知操作系统切换到另外一个线程,缺点是一个线程阻塞会导致整个进程阻塞,好处是实现简单。
b.抢占式线程调度
每个线程由系统来分配执行时间,JAVA使用抢占式调度,通过设置线程优先级(1-10)可由设置指定线程分配更多执行时间,优先级高的线程会被操作系统优先执行。
(3)状态切换
Java语言定义了5种线程状态.
image.png
网友评论